Discover millions of ebooks, audiobooks, and so much more with a free trial

Only $11.99/month after trial. Cancel anytime.

Java with TDD from the Beginning
Java with TDD from the Beginning
Java with TDD from the Beginning
Ebook439 pages4 hours

Java with TDD from the Beginning

Rating: 0 out of 5 stars

()

Read preview

About this ebook

Today there is no shortage of books and courses with which to learn the Java programming lan­guage. And there are also books and courses with which to learn how to do test-driven development (TDD) in Java.

But there seem to be no books or courses that teach Java with TDD from the very beginning. The common wisdom seems to be that the students must learn the fun­da­men­tals of Java programming before they can even be told about ap­ply­ing TDD to Java programming.

Before the students can even think about writing a test, they have to know all the Java primitive data types, how to write For loops, single-line com­ments, multi-line comments, etc. They also have to know the code name of the original Java pro­to­type, and lots of other Java history "trivia."

Or do they? Clearly there are some things students must know about Java before they can under­stand what a JUnit test is. But maybe there are certain things that are normally con­sid­ered very basic, like the characteristics of every prim­i­tive data type, that can be put off to until after the stu­dent has learned the basics of TDD.

In this book, I'm trying to figure out what is the minimum amount of information about the basics of Java syntax and the principles of object-ori­ent­ed programming that stu­dents need to know before they can start learning the basics of testing Java programs with JUnit.

The principles of TDD easily carry over to other testing frameworks, like TestNG and the testing framework the author has created.

LanguageEnglish
Release dateMay 16, 2023
ISBN9798215525401
Java with TDD from the Beginning
Author

Alonso Delarte

Composer of music for string quartet and orchestra, the first composer ever commissioned to write a concerto and a symphony through eBay. Finalist in the Knight Arts Challenge Detroit 2013 for a project to run an ice cream truck around town playing classical music, including Anton Bruckner's March in E-flat major.

Read more from Alonso Delarte

Related to Java with TDD from the Beginning

Related ebooks

Programming For You

View More

Related articles

Reviews for Java with TDD from the Beginning

Rating: 0 out of 5 stars
0 ratings

0 ratings0 reviews

What did you think?

Tap to rate

Review must be at least 10 words

    Book preview

    Java with TDD from the Beginning - Alonso Delarte

    Chapter 0: Basic Concepts and Setup

    In this chapter, we're going to go over some very basic and very general programming concepts that apply to Java and other programming languages. Then we'll go over how to set up your computer for Java programming and testing.

    Maybe you already know these basic programming concepts and maybe you already have your computer all ready and set for Java programming. Just to be sure, though, let's go over this just the same. Be sure to read this chapter, or at least skim the headings and the terms in bold, even if you think you've got everything set up already.

    A few very basic programming concepts

    Every computer program boils down to a sequence of numbers. The numbers in the sequence have significance according to where they occur in the sequence.

    For example, in a program for a particular physical central processing unit (CPU) chip, the number 247 (hexadecimal F7) might be an instruction to multiply if it occurs where an instruction is expected. But 247 could also be just some number that is to be loaded into a register prior to multiplication or some other operation.

    Multiplying 247 by itself wouldn't be a simple matter of writing F7 F7 F7 in the program. To multiply 247 by itself would probably require instructions to load 247 into two separate registers, then maybe another instruction to carry out the multiplication on those two registers.

    A physical computer chip like the Intel 80386 or the Intel Core i9 takes instructions in the form of numbers called opcodes. The Java Virtual Machine (JVM) also has instructions with opcodes, like for example 104 for an integer multiplication operation. These opcodes have mnemonics, like imul for 104, but programming in opcode mnemonics is a laborious and error-prone process.

    Languages like C++ and Java allow us to program using instructions that read more like English than the opcode mnemonics ever could. You write your computer program in a source file, which is a plain text file with a program written according to the rules of the programming language.

    Then you use the compiler for the pertinent programming language on the source files to create an executable, which is a file that your computer can run. If there are certain errors, such as syntax mistakes or misplaced source files or dependencies, then compilation will fail and you will have to correct the errors in order to make the executable.

    A program might compile but the compiler might issue some warnings, which are problems not serious enough to prevent compilation but serious enough to cause problems under certain circumstances.

    To create even the smallest Java program, you're going to need to define at least one class. I will explain what classes are in the next chapter. Here, I'll just tell you the bare minimum required to define a class:

    class SomeClass {}

    That's it. Eighteen characters, that's the whole of the SomeClass.java file. We can make it one character shorter still by deleting the second space. The first space is not negotiable, however, the Java compiler requires it to distinguish between the reserved word class and the name of the class.

    The opening curly brace means begin and the closing curly brace means end. Those are reserved words in some old programming languages — punch cards had a very limited character set.

    You can replace SomeClass with just about any other name you want, subject to some restrictions. Note that the Java compiler is case-sensitive. Theoretically, we could name one class SomeClass and another class someClass, and the compiler would probably accept both.

    But by convention, Java classes are named with words joined together with the first letter of each word capitalized, using only ASCII letters. This is a convention worth following most of the time, in my opinion.

    Spaces are not allowed in class names, so we can't name the example Some Class. Underscores, however, are allowed in class names, so the example could be Some_Class. Class names with underscores are usually used by arrogant Python programmers with no respect for Java's conventions.

    You can also draw from the wider range of Unicode characters, but this could lead to unexpected problems when sending source files to others.

    Java class names can also include digits, provided the digit is not at the beginning, e.g., ISBN10 would be valid but 10DigitISBN would not be.

    The one-line SomeClass shown above should compile, but it doesn't seem to do anything. Your class needs to have what are called "methods," subroutines that can be called from the same class and maybe from other classes, and which may or may not return anything.

    I've never liked the term method to refer to a subroutine, in part because method has a more generic meaning in the English language, and in part because it places way too much emphasis on deep class inheritance (I'll give an example of deep class inheritance later on in this book).

    And in any case I almost always like to distinguish between subroutines that return a value of a specified data type and subroutines that don't. These are functions and procedures. In the Pascal programming language, function and procedure are reserved words. Java does not reserve method.

    A function returns a value and therefore has a return type, or at least it does in a strongly typed language like Java. Here's a very simple function in Java, to calculate the area of an ellipse:

    public static double

            ellipseArea(double a, double b) {

        return Math.PI * a * b;

    }

    A double is a double-precision 64-bit floating point number. This is a function that takes two 64-bit floating point number parameters, a and b, and returns a 64-bit floating point number. Then, from elsewhere within the same class, this function can be called like this:

    double area = ellipseArea(2.5, 1.5);

    The answer is given as 11.780972450961723, which ought to be precise enough for most applications. Since this function is marked public, it can be called from other classes, but we need a few more characters to make the call. I won't explain static just yet.

    A procedure does not return a value and therefore does not have a return type (or it can be said to be of return type void). Java requires the return type of a function to be explicitly declared, as I showed with ellipseArea(), while procedures are declared with the reserved word void as the return type. Very little is needed to declare a procedure.

    class SomeClass {

     

        void actuate() {}

     

    }

    Since procedures don't return anything, there's no need for a Return statement. This procedure seems to not actually do anything, but we do have something here that can be called from this class or from some other class.

    Like most compilers, the Java compiler is indifferent to indentation. The indentation is for our sake, to make it easy to understand the scope of variables and other things.

    A variable is a data item that is allowed to change value over the course of its scope. A Java variable can be declared and initialized with the following syntax:

    type identifier = expression;

    Take note of the semicolon. The Java compiler requires it at the end of every assignment statement, every reassignment statement, and also at the end of some other statements. The semicolon is almost always required, even when a statement is the last statement before a closing curly brace.

    For example,

    class SomeClass {

     

        void actuate() {

            int a = 10;

            a = 11;

            a = 12;

        }

     

    }

    When actuate() is called, the variable a is declared and initialized to 10, then changed to 11 and then to 12. When actuate() exits, the value of a is forgotten, making this a pointless procedure. We could change the scope of a thus:

    class SomeClass {

     

        int a = 0;

     

        void actuate() {

            a = a + 1;

        }

     

    }

    Then the variable a becomes a field of SomeClass. An explanation of fields will have to wait to the next chapter. Here, suffice it to say that the variable a exists before actuate() is ever called, and continues to exist after actuate() exits.

    Some newer programming languages have a feature called semicolon inference, in which the compiler infers where the semicolon ought to go so you don't have to type it.

    That involves a trade-off. On the one hand, I sometimes forget to put in semicolons, so I appreciate that the compilers for those newer languages can infer where a statement ends. But on the other hand, I appreciate Java's flexibility of being able to split a long line into shorter lines without worrying that it might cause problems for semicolon inference. You might appreciate that, too, if you're reading this on a cell phone.

    In both of the actuate() examples so far, the variable a exists at the broadest scope it's defined in, and in scopes enclosed within that scope. The scopes are delineated by matching pairs of curly braces (it would be a syntax error to define the variable a outside the outermost pair of curly braces). But this starts to get into concepts I would rather hold off on until the next chapter.

    A subtler change of scope would be something like this:

    class SomeClass {

     

        void actuate(int a) {

            a = a + 1;

        }

     

    }

    But this is pointless because the value of a is copied so that whatever changes actuate() makes to it are not reflected in the caller's original — much more detail on this in a couple of chapters. So actuate() needs to be a function, not a procedure.

    Also, we should make it static, like ellipseArea(), so that it can be called without instantiating the containing class (I'll explain instantiation in the next chapter). And it would help for actuate() to be called something more meaningful. The parameter can also be renamed.

    class SomeClass {

     

        static int increment(int number) {

            number = number + 1;

            return number;

        }

     

    }

    Now we can call this static function from either within SomeClass itself or from some other class. Here's how it might look if we called it from some other class:

    int myNumber = 28;

    int anotherNumber

            = SomeClass.increment(myNumber);

    Note that the name of the parameter in the caller doesn't have to match the name of the parameter in the called function. The important thing is that the data types match, because Java is strongly typed. Either they are the exact same data type, or the type the caller sends can be converted to the type the called function expects, as I'll explain in later chapters.

    It could happen that the caller ignores the result of the function call, but that's something we're not going to be concerned with at this point.

    Renaming things, like changing the name of actuate() to increment() is one kind of refactoring, the process of making changes to a program so that it is more efficient or more elegant internally without necessarily changing what the program does externally. Refactoring is a big part of TDD.

    An integrated development environment (IDE) makes refactoring very easy, so that you can rename things once without having to hunt down every single occurrence of an identifier. Our program might fail to compile if we neglect to change just one occurrence of an old identifier to the new identifier, so it's better to let your IDE take care of it.

    The int in these examples refers to one of the primitive data types of the JVM. A primitive data type is rigidly defined by the virtual machine specification to have a specific range and size, it has specific virtual machine opcodes associated with it, and it can't be broken down into component types.

    The JVM has eight primitive data types. No more primitive data types can be defined without redefining the JVM. By the way, the Common Language Runtime (for C#, Visual Basic, and others) has more primitive data types than the JVM, but not too many more, and probably no more will ever be added.

    The int data type in the JVM is for 32-bit (4-byte) signed integers. The minimum int is −(2³¹) and the maximum is 2³¹ − 1. I don't expect you to memorize any of that. I'll put it in an appendix, plus there are various other references you can look to if you ever have a pressing need for it.

    The primitive data types stand in contrast to reference types, which we will delve into in the next chapter. The JDK has hundreds if not thousands of defined reference types, and you can define plenty more. Just doing the basic Hello World exercise (coming up later in this chapter), you will define a reference type.

    There are restrictions on what you can name your classes that I haven't mentioned yet, such as that you can't have two distinct classes with the same fully qualified names.

    But, thanks to packages, you don't have to worry too much about naming conflicts with classes from the JDK. Classes from the JDK have fully qualified names that begin with java. or javax., whereas the classes you define might match the short names of classes in the JDK but they should not match have fully qualified names of classes in the JDK.

    The most famous reference type in Java has the fully qualified name java.lang.String, but we can almost always refer to it as just String. An instance of String holds together a sequence of characters. Each character in a String is of the primitive type char.

    A char holds 16 bits (2 bytes). Some Unicode characters, like the vast majority of emoji, require 17-bits, but there's a workaround so that you can use those characters in your Java programs. You can look at the details of that if it interests you.

    The important thing here is that a String instance can vary greatly in size. It can be as short as Hello, world (twelve characters) or even (zero characters), or it can be longer than the plain text of War and Peace, though I'm not sure how much longer.

    Instances of reference types are passed by reference, while primitives are passed by value. For example, if a function takes an int as a parameter and it receives 17290 (00 00 43 8A in hexadecimal), the JVM will copy those four bytes from where the caller has them to a location for the procedure's use. If that function takes a String parameter, the JVM will not copy the characters that make up that String, however many they may be, but instead it copies the memory pointer.

    This means that theoretically a function that takes instances of reference types as parameters could change the values of those parameters. That's never the case with String, for reasons that I'll explain much later on.

    But if your program needs to operate on texts as long as War and Peace, you should probably use something more heavy duty than String that can modify its contents in place if necessary, like maybe StringBuilder.

    Regardless of whether a variable, parameter or return type is a primitive type or a reference type, the Java compiler requires types to be declared before it can proceed with compilation.

    A programming language that requires all variables to be declared with a data type and forbids reassignment to incompatible types is said to be a strongly typed language. Java is strongly typed, though with some caveats I will get into in a later chapter.

    A programming language that allows such reassignments willy-nilly is weakly typed. Python is weakly typed, and so is the so-called JavaScript. Weak typing is quite convenient, at least until it causes a problem. And then it's a major pain in the neck.

    Almost every programming language has a syntax for comments, which are remarks that are ignored by the compiler. Comments are for information for other humans, like copyright information and task reminders. Like C++, Java has two kinds of comments: single-line comments and potentially multi-line comments:

    int a = 10; // This is a comment to end of line

    int b = 12; /* This is a comment from opening

    until the closing characters */ int c = a + b;

    As far as the compiler is concerned, the above three lines are the same as the three lines below:

    int a = 10;

    int b = 12;

    int c = a + b;

    There is a third type of comment that is recognized by the Javadoc tool and by your IDE: documentation comments, which are also ignored by the compiler the same as ordinary multi-line comments. The Javadoc tool looks for multi-line comments that have two consecutive asterisks at the beginning, then it generates HTML pages based on those comments.

    If you ever look up something about the JDK, you will surely come across HTML pages generated by the Javadoc tool. Your IDE can also display documentation comments formatted much like the HTML pages would be in your Web browser, whether those come from the JDK, your own program or from a third party library or framework.

    I had hoped to avoid all mention of comments in this book until close to the end. But you're almost certainly going to see comments even if I didn't write any in this book, so you need to know what they look like and what they're for.

    Comments draw rather extreme opinions from experienced programmers. Some say you should comment your programs profusely, that somehow that will be useful to you at some point down the line. Others say you should never ever write comments, because such comments can easily become obsolete.

    Some programmers in the latter camp might allow documentation comments while still forbidding all other kinds of comments. Unfortunately, documentation comments can also get out of sync with what they document.

    The right viewpoint on comments, I think, is in the middle. You should write comments, but only when you can make it clear for how long the comments are supposed to be retained. Not all comments should persist for the entire life cycle of the program.

    License headers and Javadoc comments are more or less permanent. There is another type of comment which your IDE recognizes, the To Do comment. A To Do comment indicates something that needs to be done but hasn't been done yet. For example:

    // TODO: Correct the formula

    k = 2.0 * m * v * v;

    Clearly in this example the To Do comment is to be deleted as soon as the formula is corrected. It's possible that a programmer corrects the formula but neglects to delete the comment. But at least the major IDEs make it very easy to pinpoint and review To Do comments.

    An outdated To Do comment is less likely to confuse you later on than a comment that is not especially marked in some way.

    In the example, the presence of TODO: in the comment allows more for the possibility that the formula has already been corrected but the programmer who corrected it neglected to remove the comment. Then, if the formula is indeed correct, all that needs to be done is a quick verification followed by a deletion of the outdated comment.

    Okay, that's enough theory for now. We need to get you seeing these concepts in action firsthand. That means having the Java runtime and development kit installed on your computer, along with a suitable IDE.

    Choosing a Java version

    Before you can start programming in Java on your computer, before you can even start using an IDE, you need to have not just the Java Runtime Environment (JRE) installed on your system, but also the Java Development Kit (JDK). Those are two separate, but closely related things.

    Perhaps the easiest way to check whether you have those is at the command line. In Windows 10, press Windows-R and type cmd.exe in the Run dialog box. In macOS Catalina, search for Terminal. Here's how it might look on Windows 10 if your computer has neither the JRE nor the JDK:

    C:\Users\AL>java -version

    'java' is not recognized as an internal or

    external command, operable program or batch

    file.

    The not recognized message means you need to download and install the JRE and the JDK. The version of the JRE and the JDK should match, though in some cases a slight discrepancy in the minor version is acceptable (e.g., JRE 1.8.0_281 works well with JDK 1.8.0_241). Because of security concerns, you don't want your JRE to lag behind your JDK.

    But which major version should you choose? I suggest you go with Java 8 (Java 1.8 point something). It's new enough to have most of the features one would expect in a modern programming language, but old enough that it has a good deal of official and unofficial documentation.

    I get the feeling that the vast majority of professional Java programmers are still using Java 8 even though Java 20 is available now. Also, a lot of tools and third-party libraries fully support Java 8, whereas you might run into unexpected problems with Java 9 or later. These problems might be minor annoyances, or they might be deal-breakers.

    Given the choice between reinventing an essential third-party library for Java 9 or simply reverting back to Java 8, which would you choose? As interesting as the former might seem, I think I would go with the latter. Though thanks to backwards compatibility and the major tools keeping up with the latest versions of Java, you might never have to make such a choice.

    If I write a Scala version of this book, I would probably instruct readers to go with Java 8, because, as of February 2020, the Scala website explicitly lists Java 8 as a prerequisite for Scala 2.13.1. However, when I checked again on May 2020, I saw that now Java 11 is supported for Scala 2.13.2.

    The most important change in Java 9 was the addition of modules. I'll say a little more about modules later on because the feature is a radical change that will definitely impact those of you trying to follow along with Java 9 or later.

    Java 9 also added a local read-evaluate-print-loop (REPL) tool called JShell. Scala had its own local REPL from the very beginning, though Java 1.3 compiler author and Scala inventor Martin Odersky doubts a REPL is as useful to Java as it is to Scala.

    Java 9 also features some compiler improvements that only professionals working on very large projects might notice. Some other compiler improvements are of interest to authors of Java compilers, who generally wrote Java compilers in C or C++ and perhaps wished they could write them in Java instead.

    Other significant Java 9 features include a new JavaScript runtime for Java, an HTTP/2 client API, and garbage collection changes. But I probably won't mention these again.

    Also, Java 9 takes a change to interfaces in Java 8 one logical step further. That is something I will be mentioning again later, elaborating on it. Please remind me if I forget about it.

    With Java 9, Oracle introduced the concept of Long Term Support (LTS) releases, and retroactively designated Java 8 an LTS release. This means that Oracle will continue to support Java 8 even after some later versions are released. Premier support for Java 8 is slated to end March 2022, and extended support will continue until at least December 2030.

    Java 9 was not designated as an LTS release. That meant that as soon as Java 10 was released, Oracle stopped supporting Java 9. So, as of March 2018, Oracle stopped supporting Java 9 but continued supporting Java 8.

    Java 10 was not designated as an LTS release either. As a consequence of this, things that depend on Java, like IntelliJ, NetBeans, Scala, etc., were in no hurry to upgrade to Java 9 or Java 10.

    Perhaps the most important addition in Java 10 was local type inference with var. That might not mean anything to you at this point. The explanation begins in the next chapter and is slightly elaborated in later chapters.

    Java 11 was the second LTS release, and the first to be designated as such from the get-go. The major feature Java 11 added was local variable syntax for lambdas; this might make no sense to you at this point and I probably won't have reason to explain it in this book.

    Java 11 also added some improvements to the String class for dealing with Unicode. I might mention these in an appendix. Also, there are improvements to make it easier to read and write String instances from files; I might mention this in a later chapter.

    Some other improvements are perhaps of interest only to compiler authors, and some others perhaps only to programmers seeking to maximize garbage collector performance. And some other improvements are too esoteric to mention here.

    Certain

    Enjoying the preview?
    Page 1 of 1