B-Side: Lo-Fi Testing
Simplified test cases
In the days when we all got our music on vinyl, a single wasn’t something you played on Spotify or Apple Music—you went down to the record store and bought it. The single would take up one side of the record, and the artist could put another song on the back. The A-side was the hit everyone knew from the radio, and the B-side was a lesser-known track — sometimes a buried treasure, like "Revolution" on the B-side of "Hey Jude".
These B-Side chapters cover some of the topics that are important, but don’t necessarily fit into the main flow of our learning.
I am actively working to complete and revise this eBook and the accompanying videos. Please consider using the following link to provide feedback and notify me of typos, mistakes, and suggestions for either the eBook or videos:
{feedback-url}
This is the working draft of a very incomplete eBook. Content will change as chapters are developed and refined. Most noticeably (other than stuff that’s just missing) is the presence of placeholder links where I haven’t quite finished videos. Check back later for a more complete version.
What’s the Point?
-
Understand the importance of testing
-
Create test cases
-
Use test cases to guide development
Source code examples from this chapter and associated videos are available on GitHub.
An important part of coding—and a tremendously important part of the software development industry—is testing our work to verify that it works as intended. This is somewhat separate from the topic of debugging, where we focus on tracking down and correcting issues once we know they are present.
Here, we want to talk about a structured approach to validating our code as we work.
Real-World Software Testing
For organizations that develop software, testing is not just a critical part of the development process, it can be a matter of staying in business. Users expect software to work, and they’ll move on to something else if it doesn’t.
In an organizated development environment, the core of a testing strategy is using automated tests to check that each part of the software works as intended. Developers use a testing framework—a library of tools and methods—to create and run unit tests. A unit test is a small piece of code that checks a specific part of the software. For example, in a video game there might be a method that adds points to the player’s score. Unit tests could be configured to automatically call the method with a variety of different values to make sure it totals the score correctly. With a few clicks, the developer can run all of the tests and vie a report of which tests passed and which failed.
A big advantage of automated testing is that it enables rigorous regression testing. Instead of just assuming that a block of code is good because we tested it when we wrote it, regression testing runs all the tests every time we make a change to the code. This way, we can catch problems that other parts of the program might cause—like suddenly passing in a negative number to that score method.
We’re beginners here, so we won’t be using a testing framework or maintaining regression tests, but it’s important to have a sense of how things work in the job market.
Test Cases
We’re going to use a very simplified version of unit testing that we’ll call test cases. A test case is a set of inputs and the expected output.
Time To Watch!
Developing and Using Test Cases
=== Using Test Cases "Properly"
You can look at test cases in two ways:
-
A hoop my professor makes me jump through
-
A technique that can help me develop my programs quicker
You can see my bias in those two options, of course, but that doesn’t mean it’s not true. Since we’re just writing out or test cases in our comment block, the person looking at your work (i.e., your professor) can’t tell if you used the test cases in a meaningful way or just tacked them on at the end.
Well, sometimes the professor can tell—if you turn in code that has a clear mistake that could have been easily caught using our test cases strategy.
Anyway, here’s how we should be integrating testing into our development process:
-
Identify the program requirements. In other words, read the problem.
-
Develop at least three test cases--including the results you expect for each—that will confirm the program meets those requirements. Document them at the top of your code.
-
If necessary or helpful, write the steps you went through to develop the test cases. This should provide a great guide for the code you’ll need to write.
-
-
Developing your code in iterations. Huh? We mean, write a little code and test it. Fix that little bit if necessary, then move on to the next piece. That testing should use the inputs from your test cases. The program won’t produce the expected output/results, but that’s okay. You’re going to be using something to test your code, so it might as well be your test cases.
-
Keep developing your program until all of your test cases pass.
-
Review the requirements to make sure you haven’t missed anything.
-
Bask in the glory of your "A" on the assignment
=== Check Yourself Before You Wreck Yourself (on the assignments)
Chapter Review Questions
-
What are the two main parts of a test case, and why is it important to define them before you start writing code?
-
Why is it not enough to test your program with just one set of inputs? Give an example of an “edge case” that might catch a bug.
-
How can writing your test cases early help you figure out what variables or calculations your program will need?
Sample answers provided in the Liner Notes.
== B-Side: Lo-Fi Software Development
A brief look at the world of software development
In the days when we all got our music on vinyl, a single wasn’t something you played on Spotify or Apple Music—you went down to the record store and bought it. The single would take up one side of the record, and the artist could put another song on the back. The A-side was the hit everyone knew from the radio, and the B-side was a lesser-known track — sometimes a buried treasure, like "God Only Knows" on the B-side of "Wouldn’t It Be Nice".
These B-Side chapters cover some of the topics that are important, but don’t necessarily fit into the main flow of our learning.
I am actively working to complete and revise this eBook and the accompanying videos. Please consider using the following link to provide feedback and notify me of typos, mistakes, and suggestions for either the eBook or videos:
{feedback-url}
This is the working draft of a very incomplete eBook. Content will change as chapters are developed and refined. Most noticeably (other than stuff that’s just missing) is the presence of placeholder links where I haven’t quite finished videos. Check back later for a more complete version.
=== What’s the Point?
*
*
Source code examples from this chapter and associated videos are available on GitHub.
=== Programming Paradigms
Imperative vs. Declarative
Object-Oriented, Functional, etc.
=== Version Control
What is Git, what’s a repository, why we use it
Local vs. remote, commits, pushes
=== Software Development Process (SDLC content from OOP book)
Learning to write code means creating a lot of programs—mostly small, straightforward programs at first. Remember those awful word problems where a train leaves Chicago traveling 40 mph, and another train leaves Denver at 35 mph? That kind of stuff; but in my course, we don’t get too caught up in the math part of it. But we care a lot about understanding the requirements of a program and implementing it successfully.
As our programs become bigger and more complex, we’ll need to work within a deliberate design and implementation process in order to keep ourselves organized and focused. Even the smaller programs we’ll develop while learning the basics will benefit from a thoughtful approach beyond just opening a new file and starting to type. It ensures that we use our time efficiently. And when we are faced with solving a programming problem that really intimidates us, the process will help make the task more approachable.
For big or small projects, a good general approach to software development is:
- Analysis
-
Identify the goals and scope of the program. As a rule, keep it small and focused—we can always add features later. Ask yourself, What does this program need to do?
- Testing Plan
-
Determine how the final program will be tested. The testing plan will be useful, but most importantly, taking the time to establish a specific testing plan ensures that we thoroughly understand the program before we begin writing code. If we don’t know how the program will work, we’re not yet ready to begin coding. Ask yourself, How will I make sure the program works correctly?
- Implementation
-
Write and test the code. We say that this is an iterative (or "repeating") process, meaning we’ll write and test one small piece, staying with it until we know it’s good. Then we’ll move on to the next piece and repeat. Ask yourself, What code do I need in order to get the next part of the program working?
- Revise or maintain
-
If our needs or program requirements change, we’ll need to go back to the first step and begin planning the next version. If not, we’ll need to monitor that the program continues to perform as expected over time. Ask yourself, What’s next for this program?
We’ll flesh out this process as we go—and as our programs become more advanced.
=== Programming on a Team
Agile, Scrum, and Sprints
=== Check Yourself Before You Wreck Yourself (on the assignments)
Chapter Review Questions
1.
2.
3.
Sample answers provided in the Liner Notes.
== Liner Notes
Sample answers to "Check Yourself…" questions
When you buy an album on vinyl, the liner notes (or "jacket notes") are the information that’s printed on the back of the sleeve or, for music on CD or cassette, in a booklet inside the plastic case. Basic liner notes are just the song titles, and the names of the artists and producers. But sometimes you get lucky, and the liner notes have blurbs written by the artists, or maybe even the complete lyrics to the songs. In other words, a cheat sheet that really helps you understand the music.
These liner notes have answers and basic explanations to the Check Yourself… questions from the chapters. These aren’t meant to be comprehensive answers, just a little information so you can confirm you’re…well, I hate to say it, but…on the right track.
The review questions are intended to be a little open-ended, so your answers will vary from these.
=== Soundcheck
-
What tools do you need to develop C# programs?
You’ll need an IDE and the .NET SDK.
-
What does IDE stand for and what is it’s role in software development?
IDE stands for Integrated Development Environment. It’s the software you’ll use to write your C# code.
-
What IDE did you choose? Why?
Anything is fine, as long as it supports C#! Popular choices include Visual Studio Community, Visual Studio Code, and JetBrains Rider.
=== 1. Computers and Coding
-
What is the primary role of a programming language in the context of computer programming?
The primary role of a programming language is to act as a bridge between human language and machine language. It allows humans to write instructions in a way that is easier for them to understand and use, which can then be accurately translated into machine language that a computer can execute.
-
What happens when you run a C# program? Describe that process in simple terms.
When you run a C# program, your code gets compiled (translated) into something the computer can understand, then the computer follows your instructions step by step and shows the results (like printing text or doing math).
-
What’s the difference between Console.Write() and Console.WriteLine()? When would you use each?
Console.Write() prints text and keeps the cursor on the same line. Console.WriteLine() prints text and moves the cursor to a new line. Use Write() when you want output to continue on the same line, and WriteLine() when you want it to start a new line afterward.
-
How would you explain what a “program” is to an 8-year-old?
A program is like a list of instructions you give to a computer—kind of like a recipe. The computer follows the steps exactly to do something, like showing a message or solving a puzzle.
-
Explain the difference between compiled and interpreted programming languages. Describe how C# is both compiled *and interpreted.*
-
Compiled languages are those where the source code is translated into machine language by a compiler before the program is run. This machine language file can then be executed directly by the computer. An example of a compiled language is C.
-
Interpreted languages are those where the source code is translated into machine language on the fly, as the program is running, by an interpreter. An example of an interpreted language is Python.
-
C# is a somewhat unique case, as it is both compiled and interpreted. The source code is first compiled into an intermediate language called CIL, which is then interpreted by the .NET runtime (CLR) when the program is executed. This allows C# to have some of the performance benefits of compiled languages while still being able to run on different platforms.
-
=== 2. Variables and Data Types
-
What is a variable, and why do we use them in programming?
A variable is a named storage location in a program that holds a value which can change as the program runs. We use variables to store, update, and reuse data.
-
Which C# data type would you use for each of the following?
-
The number of people in The Beatles →
int
-
The full name of the Beatles bass player →
string
-
Whether the Beatles are a great band (yes or no) →
bool
-
The price of Sgt. Pepper’s Lonely Hearts Club Band on vinyl →
double
-
John Lennon’s middle initial (it’s W, incidentally) →
char
-
-
Write a line of code that declares a variable named importantYear and sets it to 1964.
int importantYear = 1964;
-
What will the following code print? Why?
1 2 3
string drummer = "Pete Best"; drummer = "Ringo Starr"; Console.WriteLine(drummer);
The code will print "Ringo Starr" because the variable
drummer
is first set to "Pete Best" and then changed to "Ringo Starr". The most recent value assigned to the variable is what gets printed. -
What happens if you try to store a word (like "hello") in a variable declared as
int
?The program will not compile. You can’t assign a value of one data type to a variable of another (incompatible) data type. You also can’t change the data type of a variable after you’ve declared it.
-
What’s wrong with this variable name:
4ladsFromLiverpool
?A C# variable name can’t start with a number. You could call it
fourLadsFromLiverpool
instead. -
True or False: You must always assign a value to a variable at the same time you declare it
False. You can declare a variable and assign a value to it later:
1 2
int albums; albums = 12;
-
What are compound assignment operators in C#? Provide examples of how they are used in arithmetic operations.
Compound assignment operators combine an arithmetic operation with assignment. Examples:
1 2
int numBeatles = 5; numBeatles -= 1; // Rest in peace, Stuart Sutcliffe!
-
Write a short program that asks for the user’s name and the number of Beatles albums they own, and then prints a personalized message like: “Hello Stu, you have 5 Beatles albums.”
BeatlesGreeting.cs
1 2 3 4 5 6 7
Console.Write("What is your name? "); string name = Console.ReadLine(); Console.Write("How many Beatles albums do you own? "); int albums = Convert.ToInt32(Console.ReadLine()); Console.WriteLine($"Hello {name}, you have {albums} Beatles albums.");
-
10. What are the two ways to convert a
string
to a numeric type, such asint
ordouble
? Show the syntax of each.You can convert a string to a numeric type using either the
Convert
class or theParse
method. Here are examples of both:1 2 3
string nineteenSixtyNine = "1969"; int woodstockYear = Convert.ToInt32(nineteenSixtyNine); // Using Convert class woodstockYear = int.Parse(nineteenSixtyNine); // Using Parse method
=== 3. Methods
-
What is a method, and why would you use one in your code?
A method is a named block of code that does something — like a mini-program inside your program. You use methods to organize your code, avoid repetition, and make your program easier to understand and manage. (Like reusable LEGO chunks.)
-
What’s the difference between calling a method and defining a method?
Defining a method is writing what the method does. Calling a method is telling it to actually run. You define once, but can call it as many times as you want.
-
What does the keyword
void
mean in a method definition?void
means the method doesn’t return anything. It just does something, like printing text, but doesn’t give a value back. -
Consider the code below. How would you call it to greet someone named "Brian Epstein"?
1 2 3 4
static void Greet(string name) { Console.WriteLine($"Hello, {name}!"); }
[source,csharp]Greet("Brian Epstein");
-
Why is it a good idea to define reusable code inside a method instead of copying and pasting it everywhere?
Defining reusable code inside a method is better than copying and pasting because it makes your code cleaner, easier to read, and less error-prone. If you need to change the code later, you only have to do it in one place instead of everywhere you pasted it. It also ensures that each copy of the code does the same thing, reducing the chance of mistakes.
-
Consider the code below. a. Overload the method by accepting the name of a band to introduce. b. Demonstrate how to call each version of the method.
1 2 3 4
static void AnnounceBand() { Console.WriteLine("Ladies and gentlemen, The Beatles!"); }
-
Overloaded method:
1 2 3 4
static void AnnounceBand(string bandName) { Console.WriteLine($"Ladies and gentlemen, {bandName}!"); }
-
Calling each version:
1 2
AnnounceBand(); // Calls the original method AnnounceBand("The Rolling Stones"); // Calls the overloaded method
-
-
What happens if you define a method with a parameter but forget to provide an argument when you call it?
If you define a method with a parameter but forget to provide an argument when you call it, the program will not compile. The compiler will give an error because it expects an argument for the parameter.
-
What do we call the variable inside a method’s parentheses? How is it different from an argument?
The variable inside a method’s parentheses is called a parameter. It’s a placeholder for the value that will be passed to the method when it’s called. An argument is the actual value you provide when you call the method. For example, in
greet("Brian Epstein")
, "Brian Epstein" is the argument, andname
ingreet(string name)
is the parameter.
=== 4. Decisions
-
Q1
Answer
=== 5. Loops
-
Q1
Answer
=== 6. Debugging and Generative AI
-
Q1
Answer
=== 7. Classes and Objects
-
Q1
Answer
=== 8. Arrays
-
Q1
Answer
=== 9. Graphical User Interfaces
-
Q1
Answer
=== B-Side: Lo-Fi Testing
-
What are the two main parts of a test case, and why is it important to define them before you start writing code?
A test case includes inputs (the data the program uses) and the expected output (the result the program should produce). Defining test cases before coding helps you understand the problem better and plan how your program will work, instead of just jumping in and hoping it all comes together.
-
Why is it not enough to test your program with just one set of inputs? Give an example of an “edge case” that might catch a bug.
One test case might only show that your code works in that specific situation. To be sure your program works in general, you need to test it with a variety of inputs, including unusual or tricky ones.
An example of an edge case: If you’re writing a program that determines if a person is old enough to vote, the input 17 should return "no"; 18 should return "yes".
-
How can writing your test cases early help you figure out what variables or calculations your program will need?
Thinking through test cases makes you figure out the steps to solve the problem. It helps you see what data you need, what formulas you’ll use, and what your program should do at each stage. It’s like using the test cases to reverse-engineer your program before you even start coding.
== Deep Cuts
WTF a "deep cut"?
Some artists value the idea of an album as a unified creative work, and they put a lot of thought into which songs go on an album—and in what order. They might want a really catchy song at the start of the album, a strong start to the second side, and a big (or thought-provoking) finish.
A song is called a "cut" because a recording master was made by literally cutting a groove into a disc, and artists would cut a song in the studio. A deep cut is a song buried deep in an album that isn’t a radio single but is still great—the kind of song that becomes a fan favorite. Check out "Loving Cup", which is smack dab in the middle of Exile on Main St. and is easily overlooked among the hits all over that album.
This section is like that: cool stuff that isn’t critical enough to include in the chapter, but might turn out to be really interesting or useful. I like the C# language a lot, and there are a ton of great features that I don’t get into because I know many of my students don’t need a bunch of extra stuff thrown at them. So this is where I can put some of that stuff. They’re brief and, where appropriate, I link to a reliable source of additional information on the topic.
And just to be clear, this is what a normal books would call the appendices…
=== Binary Numbers and How Computers Work
Deep Cut from Chapter 1 - Computers and Coding
The chapters of this eBook are subtitled using binary to show basic examples of the numbering system: 0001 is, well, 1 in decimal. And 0100 is 4 in decimal. We won’t get into binary numbers, but it’s not very complicated and is kind of interesting if you’re a nerd.
The amazing code.org project has a playlist of great, short videos on how computers work—including binary numbers—if you want to know a little more without going too crazy: How Computers Work
=== string.Format()
Method
Deep Cut from Chapter 2 - Variables and Data Types
You can also place variable values in a string using the string.Format()
method.
This method accepts a string with numbered placeholders, along with the variables you want to insert—in the order they appear in the string.
1
2
3
4
5
6
string artistName = "Elvis Presley";
int birthYear = 1935;
string output = string.Format("{0} was born in {1}", artistName, birthYear);
Console.WriteLine(output);
The {0}
and {1}
in the string act as placeholders, and then the values are added on with commas after the string.
artistName
is the first variable listed, so it goes in the first placeholder; birthYear
is the second variable listed, so it goes in the second placeholder.
Beginners might find this style a little more confusing, but programmers coming to C# from some other languages might find it familiar.
And interpoliation is a relatively new addition to C# (around 2015, I believe), so you might still see older code using string.Format()
.
=== Optional Arguments
Deep Cut from Chapter 3 - Methods
C# allows us to specify that some arguments are optional. By default (the way we’ve defined parameters so far), a method call must provide a value for each parameter in the method. However, you can also assign a default value to a parameter; that makes it optional.
OptionalArgument.cs
. A method with an optional argument.1
2
3
public static double TotalBill(double meal, double tipPercentage = 20.0) {
return meal + (meal * tipPercentage/100);
}
When calling TotalBill
, we can provide one argument (price of the meal) or two (price of the meal and the tip percentage).
If we don’t provide tipPercentage
, the method will use 20.0
for that parameter.
double amount = TotalBill(100.0); // amount = 120.0
double amount2 = TotalBill(100.0, 18.0); // amount2 = 118.0
If you use optional parameters, they must be at the end of the parameter list. In other words, in the example above, I couldn’t have the optional tipPercentage before meal .
|
Learn more about optional parameters in the official Microsoft documentation: Optional Arguments (C# Programming Guide)
=== Named Arguments
Deep Cut from Chapter 3 - Methods
Another nice C# feature related methods is named arguments. Named arguments allow you to specify the name of the parameter you’re providing a value for, so you can list the arguments in any order. Simply put the name of the parameter followed by a colon and the value you want to assign to it.
It means you don’t have to remember the order of the parameters in the method as long as you remember the names.
NamedArguments.cs
. A method call using named arguments. 1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class NamedArguments
{
public static void Main()
{
double amount = TotalBill(tipPercentage: 18.0, tax: 1.0, meal: 100.0);
Console.WriteLine(amount);
static double TotalBill(double meal, double tax, double tipPercentage)
{
return meal + (meal * tax / 100) + (meal * tipPercentage / 100);
}
}
}
Learn more about named arguments in the official Microsoft documentation: Named Arguments (C# Programming Guide)
=== Nested Method Calls
Deep Cut from Chapter 3 - Methods
Coming Soon!
=== Recursion
Deep Cut from Chapter 5 - Loops
Coming Soon!
== Acknowledgements
This eBook and accompanying videos were created on macOS using a variety of free tools and resources.
=== License
This work is licensed under CC BY-NC-SA 4.0. You are free to remix, adapt, and share it—just don’t sell it, and please give credit. If you build on it, share your version under the same license.
=== Tools Used in Production
-
The text and most source code examples were written in asciidoc format using Visual Studio Code, a free, open-source IDE.
-
The HTML was generated with Asciidoctor, a free, open-source tool for converting AsciiDoc to HTML and other formats.
-
The source repository and published HTML version of the eBook are hosted on GitHub and GitHub Pages, respectively.
-
Videos embedded in the course were created with OBS Studio, Canva, and Davinci Resolve. A few visuals were created in Apple Keynote.
=== Visuals & Design
-
The CSS (visual style) for the HTML version of this eBook is inspired by the Architect theme for GitHub Pages.
-
Icons used throughout this book are by Guilherme Furtado from The Noun Project, used under the Creative Commons Attribution 3.0 License.
-
Some illustrations and visuals were created with help from DALL-E via ChatGPT and Microsoft Copilot.
=== Music Stuff
I’m not a musician, and really am no more of a fan than the average person. But I am nerdy about music history, and especially the cultural impact of music.
A lot of the stupid little references in the book and videos are just things I happen to know; in some cases I just jump over to Wikipedia and find something that’s interesting and is documented well enough that I trust it to be true.
If you’re interested in music (well, rock music) history, there are a few things I can recommend:
-
A History of Rock Music in 500 Songs by Andrew Hickey. This is an amazing ongoing podcast that is doing just what the title says, starting all the way back in 1939 and moving chronologically from there. It’s incredibly well-researched, and Andrew does such a good job of using these individual songs to create a cohesive narrative.
-
Peter Guralnick is a music historian and biographer who specializes in early rock and roll. His two-volume biography of Elvis Presley (Last Train to Memphis and Careless Love) is widely considered the definitive work on Elvis. His biography of Sam Phillips (Sam Phillips: The Man Who Invented Rock 'n' Roll) is also excellent and covers Sun Records and the early days of rock and roll. I haven’t gotten to his Sam Cooke biography yet, but it’s on my list.
-
Mark Lewisohn is a Beatles historian and author of the Tune In, which is the first book in a planned three-volume biography of the Beatles. It’s a massive book, but it’s incredibly well-researched and provides a detailed look at the Beatles' early years. The extended edition is an astonishing 1,700 pages long, and only covers to the end of 1962—before the band even released their first album! If you want a deep dive into their time in Hamburg, this book is for you. Beatles fans everywhere are impatiently awaiting the next volume.
-
Cocaine & Rhinestones by Tyler Mahan Coe. Another well-researched podcast, this one focuses on country music. I don’t listen to country music, but based on the title alone, I figured it was worth a listen. Turns out, it’s great—even for someone who doesn’t really like country.
← Previous: Graphical User Interfaces | ↑ Up: Lo-Fi C#: An Introduction to Coding