Detailed Table of Contents
Can explain debugging
Debugging is the process of discovering defects in the program. Here are some approaches to debugging:
Exiting process() method, x is 5.347. This approach is not recommended due to these reasons:
Can step through a program using a debugger
This video (from LaunchCode) gives a pretty good explanation of how to use the IntelliJ IDEA debugger.
Can explain the need for avoiding error-prone shortcuts
It is safer to use language constructs in the way they are meant to be used, even if the language allows shortcuts. Such coding practices are common sources of bugs. Know them and avoid them.
Can improve code quality using technique: use the default branch
Always include a default branch in case statements.
Furthermore, use it for the intended default action and not just to execute the last option. If there is no default action, you can use the default branch to detect errors (i.e. if execution reached the default branch, raise a suitable error). This also applies to the final else of an if-else construct. That is, the final else should mean 'everything else', and not the final option. Do not use else when an if condition can be explicitly specified, unless there is absolutely no other possibility.
Bad
if (red) print "red";
else print "blue";
Good
if (red) print "red";
else if (blue) print "blue";
else error("incorrect input");
Can improve code quality using technique: don't recycle variables or parameters
Bad
double computeRectangleArea(double length, double width) {
length = length * width;
return length;
}
def compute_rectangle_area(length, width):
length = length * width
return length
Good
double computeRectangleArea(double length, double width) {
double area;
area = length * width;
return area;
}
def compute_rectangle_area(length, width):
area = length * width
return area
}
Can improve code quality using technique: delete dead code
You might feel reluctant to delete code you have painstakingly written, even if you have no use for that code anymore ("I spent a lot of time writing that code; what if I need it again?"). Consider all code as baggage you have to carry; get rid of unused code the moment it becomes redundant. If you need that code again, simply recover it from the revision control tool you are using. Deleting code you wrote previously is a sign that you are improving.
Can improve code quality using technique: minimize scope of variables
Minimize global variables. Global variables may be the most convenient way to pass information around, but they do create implicit links between code segments that use the global variable. Avoid them as much as possible.
Define variables in the least possible scope. For example, if the variable is used only within the if block of the conditional statement, it should be declared inside that if block.
The most powerful technique for minimizing the scope of a local variable is to declare it where it is first used. -- Effective Java, by Joshua Bloch
Resources:
Can improve code quality using technique: minimize code duplication
Code duplication, especially when you copy-paste-modify code, often indicates a poor quality implementation. While it may not be possible to have zero duplication, always think twice before duplicating code; most often there is a better alternative.
This guideline is closely related to the DRY Principle .
Can explain the need for early developer testing
Delaying testing until the full product is complete has a number of disadvantages:
Therefore, it is better to do early testing, as hinted by the popular rule of thumb given below, also illustrated by the graph below it.
The earlier a bug is found, the easier and cheaper to have it fixed.
Such early testing of partially developed software is usually, and by necessity, done by the developers themselves i.e. developer testing.
Exercises
Can explain test drivers
A test driver is the code that ‘drives’ the Software Under TestSUT for the purpose of testing i.e. invoking the SUT with test inputs and verifying if the behavior is as expected.
PayrollTest ‘drives’ the Payroll class by sending it test inputs and verifies if the output is as expected.
public class PayrollTest {
public static void main(String[] args) throws Exception {
// test setup
Payroll p = new Payroll();
// test case 1
p.setEmployees(new String[]{"E001", "E002"});
// automatically verify the response
if (p.totalSalary() != 6400) {
throw new Error("case 1 failed ");
}
// test case 2
p.setEmployees(new String[]{"E001"});
if (p.totalSalary() != 2300) {
throw new Error("case 2 failed ");
}
// more tests...
System.out.println("All tests passed");
}
}
Can explain test automation tools
JUnit is a tool for automated testing of Java programs. Similar tools are available for other languages and for automating different types of testing.
This is an automated test for a Payroll class, written using JUnit libraries.
@Test
public void testTotalSalary() {
Payroll p = new Payroll();
// test case 1
p.setEmployees(new String[]{"E001", "E002"});
assertEquals(6400, p.totalSalary());
// test case 2
p.setEmployees(new String[]{"E001"});
assertEquals(2300, p.totalSalary());
// more tests...
}
Most modern IDEs have integrated support for testing tools. The figure below shows the JUnit output when running some JUnit tests using the Eclipse IDE.
Can explain unit testing
Unit testing: testing individual units (methods, classes, subsystems, ...) to ensure each piece works correctly.
In OOP code, it is common to write one or more unit tests for each public method of a class.
Here are the code skeletons for a Foo class containing two methods and a FooTest class that contains unit tests for those two methods.
class Foo {
String read() {
// ...
}
void write(String input) {
// ...
}
}
class FooTest {
@Test
void read() {
// a unit test for Foo#read() method
}
@Test
void write_emptyInput_exceptionThrown() {
// a unit tests for Foo#write(String) method
}
@Test
void write_normalInput_writtenCorrectly() {
// another unit tests for Foo#write(String) method
}
}
import unittest
class Foo:
def read(self):
# ...
def write(self, input):
# ...
class FooTest(unittest.TestCase):
def test_read(self):
# a unit test for read() method
def test_write_emptyIntput_ignored(self):
# a unit test for write(string) method
def test_write_normalInput_writtenCorrectly(self):
# another unit test for write(string) method
Resources
Can use simple JUnit tests
When writing JUnit tests for a class Foo, the common practice is to create a FooTest class, which will contain various test methods.
Suppose we want to write tests for the IntPair class below.
public class IntPair {
int first;
int second;
public IntPair(int first, int second) {
this.first = first;
this.second = second;
}
public int intDivision() throws Exception {
if (second == 0){
throw new Exception("Divisor is zero");
}
return first/second;
}
@Override
public String toString() {
return first + "," + second;
}
}
Here's a IntPairTest class to match (using JUnit 5).
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class IntPairTest {
@Test
public void testStringConversion() {
assertEquals("4,7", new IntPair(4, 7).toString());
}
@Test
public void intDivision_nonZeroDivisor_success() throws Exception {
assertEquals(2, new IntPair(4, 2).intDivision());
assertEquals(0, new IntPair(1, 2).intDivision());
assertEquals(0, new IntPair(0, 5).intDivision());
}
@Test
public void intDivision_zeroDivisor_exceptionThrown() {
try {
assertEquals(0, new IntPair(1, 0).intDivision());
fail(); // the test should not reach this line
} catch (Exception e) {
assertEquals("Divisor is zero", e.getMessage());
}
}
}
Notes:
@Test annotation.Assert.assertEquals(expected, actual) methods to compare the expected output with the actual output. If they do not match, the test will fail. JUnit comes with other similar methods such as Assert.assertNull and Assert.assertTrue.testStringConversion but when writing test methods, sometimes another convention is used: whatIsBeingTested_descriptionOfTestInputs_expectedOutcome e.g., intDivision_zeroDivisor_exceptionThrowncatch block. But if it is not thrown as expected, the test will reach Assert.fail() line and will fail as a result.Adding JUnit 5 to your IntelliJ Project -- by Kevintroko@YouTube
Resources