How to teach TESTERS programming?

Marcin Ludzia
C&F Data Driven Innovators
11 min readJun 2, 2022

What does a manual tester do to start testing automatically? They learn programming, of course. However, without experience with coding, this road can be bumpy. I often see that understanding such programming concepts as code split into classes and methods, parameters references, or basics of OOP may be hard to understand at the beginning. But they are crucial.

If you have encountered these bumps, and are looking for a way to avoid them, explore the educational roadmap I prepared for an effective and comprehensible process of teaching testers how to code.

How do testers differ from programmers?

Writing automated tests, simply put, is developing software that tests other software*. But developers and testers develop their code in different ways.
There are three key differences:

  1. Point of view (perspective)

For starters, developers and testers usually have different points of view. And that’s good, because they complement each other. While developers usually want to make the functionality work, testers focus on making sure that all users’ needs are met, and the software remains resilient to different, even exotic, users’ inputs or application use. As you can read in my other article, How to develop quality software in agile, in the case of application quality, developers usually focus on code quality. Testers, however, focus on the overall quality perception of the application as seen from the end user’s point of view.

2. Code dependency and structure

The second difference is in the code that developers and testers write. The application’s code is architecturally independent from the tests code. On the other hand, test code is architecturally dependent on application code. This means that tests follow the application. This is particularly evident in lower-level tests, but also visible in end-2-end tests.

Another aspect is that tests should be easy to implement. They should not involve any business logic. Checking different business cases should be implemented as separate tests, or as different test data cases for the same script. So, all tests should be implemented in a straightforward manner.

3. Required skills

The third difference is obvious. As a rule of thumb, to start working on tests automation, you need to know fewer programming concepts compared to application development. But this may be tricky. You may think that you will be fine with only a basic understanding of programming. This assumption usually gets verified quickly.

But at the same time, you don’t what to go through the whole Computer Science program with Assembler, Algorithms and Data Structures, etc.

So how to properly balance the need to quickly put people to work on test automation with giving them enough background so they will be able to further develop themselves as programmers?
Here is my recipe.

Roadmap for programming skills for testers

There are different programming languages and different strategies; e.g., I can see the rising popularity of JavaScript in automated tests. However, I think that Java is the language best suited for the purpose (at least for now) — especially in bigger, corporate projects. And according to Angie Jones’s statistics**, Java is still the first choice for test automation. But Java might be overwhelming at the beginning, when you need to declare a class (what is a class??), and a static function (what is static, and why?) just to display “Hello World!”. Also, the toolchain around Java might be a bit complicated for newbies.

So, we will start with Go — a simple language with a great toolchain, and some aspects that will make teaching yourself Java later easier. Then we will move to Java, the target language.

Go

  1. Data types and data manipulation

The purpose of this lesson is to show native data types, such as int (and variants, like int64, uint64, etc.), float32, float64, bool, rune, and string. The learner will need to understand different kinds of data that can be stored in different data types. It is also crucial to understand what happens if you exceed the maximum value that can be stored in a certain variable (e.g. what happens if you add 1 to int8 variable with an actual value of 127), and that float numbers cannot represent all values correctly (e.g., 0.2 is rounded, and cannot be represented precisely).

As an additional bonus, it is good to introduce a very basic pointer syntax to show what is held inside: variable, *pointerToVariable, pointerToVariable, &addressOfVariable. You can show everything in a simple hello world program followed by equally simple examples of different variables and pointers to show what is possible and what is happening under the hood in the computer’s memory.

2. Flow control

This is the foundation. In imperative languages, statements to make decisions or to repeat certain instructions are at the heart of programming. The good news for students is, these statements work the same (or are very similar) in all imperative languages. So, during this lesson, you will want to demonstrate and teach conditional statements and loops.

3. Functions

In the third lesson, the first difficult topic is introduced. Understanding the division of a program into functions, the mechanics behind how functions/methods call each other, and how they pass control and pass data (as parameters and return values) is crucial. It is good to once again use pointes to show how parameters are passed (with passing by value and passing a pointer).

But the truly difficult part for people who are learning to code is related to how they should divide a program into smaller chunks. This is not language semantics. This is about the principle of writing small, reusable functions. And I know that this might be difficult at first. And you need to spend some time practicing this. It’s worth it.

4. Tables and structs

The next topic is related to basic data structures. The main objective here is to teach how different variables can be grouped and used as single collection.

First, you need to show how a table can be declared and used to store different amounts of data of the same type. After that, you need to present the concept of the struct. This one is particularly important because it teaches many things before we move fully to Java — a classic Object-Oriented language. First, structs can help the learner think of grouping different types of data into larger entities. And because of the Golang semantics, it also teaches that this is in fact a new type (something that might not be as clear in Java at first, when you create a class). Teaching how to create and use structs also lays down foundations that will be crucial later, when learning Java.

5. Unit test

This is the first time the learner meets automated tests. In Go, unit tests are very easy to write, and you don’t need additional tools. Everything is built into the toolchain of Golang. At this point, you want to teach testers basic rules: what an automated test actually is, the basic structure of automated tests (e.g., via the triple A principle — Arrange, Act, Assert), how assertions/checks are used to obtain the test result. Unit tests are also great for the introduction of such concepts as test coverage.

Once again, while Golang here may not be as advanced as some libraries (e.g., AssertJ from Java), yet because of its simplicity, it really shows how it all works under the hood.

Java

6. The basics of Java

After the first five lessons in Golang, we move to our target programming language, which is Java. The first lesson about Java will be mostly the repetition of concepts already learned in Go, but with Java syntax. So, you will want to once again present primitive data types, flow control instructions, tables, etc. You don’t need to introduce the full concept of class yet, as this will be covered in a couple of following lessons.

The repetition of concepts that are already known has one more advantage. This makes students once again rethink everything they know. In my opinion, and from what I can observe, getting those fundamentals right is vital for understanding further topics. So, it’s good to encourage students to write some small projects with the knowledge they already have.

7. Classes and Object-Oriented Programming

It is the middle of the programming course, so it’s a good time to introduce and explain the concept of Object-Oriented Programming. This lesson should be mostly theoretical. But don’t provide full explanations on OOP yet. For now, focus on abstraction and hermitization: explain to your students how they should identify entities that are then translated into classes; how to encapsulate internal complexity, and provide only a public interface that can be used from outside the class. In practice, this translates into explaining the class syntax, constructors, and basic modifiers (private and public). For now, that will be sufficient. Give your students time to get used to thinking in an object-oriented way.

8. Comments and the basics of clean code

At this moment, students know the foundations of programming. Now it’s time to introduce some good practices. And this may seem trivial or obvious, but in my opinion it isn’t. Plenty of times I saw badly written code, with variables or functions improperly named, without any comments, or with too many comments. Or with comments that don’t give you a clue about how to use the code.

So, dedicate one lesson to explain in detail how to use comments. For both: explaining how the commented code works (documentation comments), and how comments can be used to design code (function or class). It is also important to provide some basic principles of clean code, naming conventions, code formatting, etc.

9. Operations on Strings

This is one of the most important lessons in the whole course. Operations on strings are essential and commonly used in test automation. By this lesson, learners will probably have had a chance to already compare two strings, concatenate strings with the “+” operator (in different configurations, e.g. String + String, String + Integer, etc.).

Now is the time for more advanced operations. So you want to start with easy methods, like: charAt(), contains(), endsWith(), startsWith(), substring(), toLowerCase(), toUpperCase(), trim(). Then you will want to work with more advanced cases and the use of split(), replace(), and replaceAll() methods. It would be also useful to show some examples of how to use those methods with simple regex expressions.

10. Time and Date API

Next is the time for the time and date API 😊. Dates and time are commonly used in applications, so a tester needs to know how to create and manipulate these objects. And in Java you need to have a good introduction to the time and date API. Since Java has two time and date APIs, you need to specifically show which class is from which version. Because you can find many examples on StackOverflow using the old API, I often see practice tests mess up Date class objects with LocalDate class objects. So, pay close attention to specifically introducing Java 8+ classes: LocalDate, LocalTime, LocalDateTime, Period, Duration. When testing corporate applications there might also be a need to use ZonedTimeDate.

11. Tables and basic collections API

The next lesson is about collections. First, you should introduce arrays as an easy and quick way to store a list of values. The arrays, however, are rigid and cannot be easily extended. For this reason, the List interface and ArrayList collection should also be presented. There is probably no point in presenting LinkedList, as this would not be necessary to use in tests.

As an extension to the basic scope of this topic, the Set and Map interfaces are worth mentioning (HashSet and HashMap implementations).

12. More advanced OOP

It is time to introduce more advanced topics related to Object-Oriented Programming. During this lesson, you will want to teach about inheritance, interfaces, abstract classes, and polymorphism. These are advanced topics, and in my view, testers probably could live without them. Someone could say that a tester does not need to know polymorphism. My answer would be that in many cases, simple scripts can be developed without this knowledge. But in bigger projects, or with a more advanced user interface, advanced object-oriented concepts can be used to write reusable code for the Page Objects layer, to minimize the maintenance effort later. Anyone who has spent some time in the test automation business knows that minimizing maintenance costs is crucial.

So even though inheritance or polymorphism may look at first as something that testers don’t need, in fact, it’s good to practice them a bit. This will result in a better feeling of OOP and better code that is easier to maintain.

13. Unit tests in JUnit/TestNG

Once again during this course, we will dedicate a lesson to unit testing. This time in Java. Since we already covered some basics of the topic in Golang, the students will already be familiar with the theory behind it. This time you should focus on the specifics of Java unit testing and a selected assertions library. In my case, I focus on the use of TestNG assertions and annotations. Assertions are used to provide additional meta-data for tests.

From this perspective, this lesson is probably less a theoretical one about unit testing, but rather a very practical introduction to the selected library. Junit and TestNG are both good.

14. Error handling

It is almost the end of the course. However, there are still a couple of important topics to cover. The first one is exceptions and error handling. Students should be familiarized with the concept of checked and unchecked exceptions in Java. They should know which ones can occur during the execution of tests, which ones need to be handled and declared. Finally, they need to know how to properly create a try-catch block. And since assertions in tests can make the test code finish earlier than expected, try-with-resources can also be covered.

15. Network programming

This is the last lesson. Use it to introduce network programming in Java, especially the HttpClient, HttpRequest and HttpResponse classes. This is more of a theoretical introduction to programming network calls. However, it is very important that testers know some basics. In real life though, the tester would use libraries like RestAssured for 99,3%*** of the cases.

The above plan consists of 15 topics, just like a typical one-semester course at university 😊. During this time, we start teaching testers programming with a bit of Golang (to begin without overwhelming boilerplate code), then we move to Java and demonstrate some aspects of the language most commonly used in tests. To put everything in the testing perspective, rather than only teach about language semantics, we introduce some unit testing and good practices in coding to promote best practices from the beginning.

Skills related to programming are just the beginning of the path for test automation engineers. The next steps would be to teach them how to use automation libraries (like Selenium, Playwright, RestAssured, etc.), a version control system (Git), and dive deeper into TestNG/Junit. Once the test automation engineer is familiar with all these bits and pieces, he/she can start working on a production system. And as always, what matters most is practice, practice, and more practice 😊.

* A disclaimer here: if you noticed that I omitted unit tests, which are built into the production code, you are right. But even though unit tests are important and actually a basis for the testing pyramid, they are typically built by developers. And higher-level tests are usually built by testers, who for the most part look at the software from the end user’s perspective.

** Top 5 Popular Programming Languages for Test Automation of 2019 (applitools.com)

*** this is just my assumption. 😊 Not a real statistic.

--

--

Marcin Ludzia
C&F Data Driven Innovators

Digital Strategy Enthusiast & Practitioner, Software Engineering Manager