Transcripts
1. Introduction: Hey there. Welcome to the course on effective software testing
with D Net in EX Unit. Well, to start things off,
let me present myself. My name is Cayo Susa. I currently work as a
software architect, and I've been in the software development
area for over ten years now. Within this course, I hope
that I can share my expertise. That you can learn about
software testing and how to make software testing effective when it
comes to unit testing. I will also give an overview
of the fundamentals of software testing and try to help you understand
why we should test. And what are some
of the software testing types that exists? Well, first, I will start by talking about the
software testing fundamentals. We will review and explore
the testing pyramid. We will cover theory
and unit testing, integration testing,
system, and manual testing. You will be able to
understand why and when to apply each of these
testing strategies. We will also look
at a case study that involves college
scholarships. Based on this case study, we will use unit
testing techniques to test the logic and
validate some edge cases. You understand the importance of unit testing and how this
can reduce risks when it comes to making
changes to code and validation of
existing business rules. We will explore the
fundamentals of X unit and understand
why to use this tool. There we will
implement code where you will implement
unit tests and go over the X unit library and functionalities
for unit testing. As part of this course,
we will also explore other libraries that support unit testing such as
fluent assertions. We will look at common practices such as using the Visual
Studio test explorer, formatting tests and
patterns such as the a pattern and code coverage. We will then look at the
requirements for testing. We will apply unit testing based on software requirements. We will mock data access to facilitate unit testing and review our tests
with code coverage. I invite you to join me in this exciting
learning experience that will help you get
to that next level. My goal is that
after this course, you will have learned software
testing fundamentals. You will have
confidence to create unit tests using
common libraries. You will be able to effectively create unit tests in
the.net applications. Finally, I hope this
experience can give you technical growth and expertise in the software
engineering field.
2. Fundamentals of Software Testing: Hey, there. Welcome to the lesson on fundamentals
of software testing. So let's talk about why
somebody would test a software. So a software failure may compromise an individual
project or organization. Depending on the bug
of the software, an error can compromise
a whole operation. Imagine you're implementing
a financial software, and due to a major bug, millions of dollars are
lost in transactions. That can compromise
your organization, the project you're working on, and also you as an individual. Software quality. As
a software engineer, it is your responsibility to ensure quality of the
software you produce. Implementing high
quality software can also lead to growth. The third point is security. Having a secure software
is essential to protect against
malicious attacks and prevent data breaches. You can think about
testing authentication, testing authorization, penetation testing, and
a lot more testing. For costs, saving your
organization time and money, testing is one of the most
cost effective ways to ensure the quality and reliability
of your software. Finally, there are many
more other reasons that testing can help. You explicitly or implicitly see this throughout this course. So let's dig in a little bit deeper when it comes to
cost of software testing. The cost of bugs that happens in production often outweighs
the cost of prevention. Imagine a stock trading app
down for 2 hours due to a bug in code that could have been prevented with
proper unit testing. Calculate the effort that
it takes to debug an error, make the code changes, then deploy plus the
human resource cost. If no testing is considered, errors might be common in
the development process. Where Devs push code to
specific environment, QA would find defects. It would go back to the
developer that would fix it, then the code goes
to production. You might have heard that
the test is lengthy process, but practice makes perfect. Developers who have
testing as part of their development life cycle
usually have better results. What are some of the
software testing excuses? Sometimes the responsibility is pushed to the quality
assurance team or QA. However, QA cannot test
every change to code, as they are usually not aware
of all the changes in code. My software has few bugs. Even well architected
software with experienced engineers working on them should still run into bugs, especially with the
growth of the team, size, lines of code and patterns, not part of the requirements. Software requirements
should defend testing through input of
non technical requirements. Too time consuming
and expensive. Like previously
mentioned, fixing production bugs tend to be more expensive than
preventing them. Not necessary for delivery. Software testing could
be part of delivery. Tactics that automate
and run tests with code coverage analysis could be part of a delivery pipeline. Let's look at some principles. So can I test everything? Testing all scenarios
in software is not feasible due
to the number of possible combinations
and scenarios that can appear as software evolves. One must prioritize
what should be tested, and we will talk
about this when we get to the section about the
types of software testing. When should I stop testing? The goal is to figure out what types of tests
make more sense for the software that you
are developing and what part of the software
should you prioritize testing? With that, you would know when
to stop testing or when to test at all. Where are to test. Some features of software are
more critical than other, like a banking transaction
module might be higher priority than a notification section
of a software. Flawless testing. There is
no test perfect software. As **** Straw states, testing can help
find the presence of bugs, not their absence. There is no bug free software.
Finally, conditions. Different software may
require different tests. A web app may require
strategies that differ from a desktop app that differs
from a mobile app.
3. Unit Testing: Hey there. Welcome to the
lesson in unit testing. So unit tests are small, as the name implies unit. When the goal is to test a
single feature of a software, unit testing is a great choice. Unit Test validates a unit or a part of a small
section of the code. Unit tests are fast.
They're small and quick. Usually, unit test
covers a method within a class if you're working in object oriented programming. In most cases, they
test a part of the method or a behavior
of a single method. So you would have
multiple unit tests testing a single method
for multiple behaviors. Let's look at some
of the advantages. As mentioned previously,
they are fast. They should be easy to control
because they are small. They should be easy to write
because they are small. And they also serve
as documentation. Let's look at some
of these advantages. Sometimes they lack
reality because they test such a small
section of the code. Since they're testing units, it does not catch all bugs, since it's not testing methods communicating
with each other. And that's one of the
biggest disadvantages. Well, with unit test, we
can measure code coverage. By definition, a coverage
metric shows how much source code a test suite executes
from none to 100%. It is common to hear that the
higher the cold coverage, the higher the code quality. Even though cold coverage can
provide valuable feedback, it does not provide accurate
feedback on effective tests. However, it is
important to understand code coverage and to apply
cold coverage when necessary. So let's look at cold coverage. The code coverage is basically
lines of execution or lines of code and execution divided by the total
lines of code. The code or test
coverage metrics shows the ratio of these two items. And this helps us understand how much of our code
has been tested. When it comes to
branch coverage, it's basically the
branches traversed, divided by the total branches. Branch coverage shows how many of control
structures such as switch and nif
statements are traversed by at least one
test in the suite. And then we have the core
logic or issues with metrics. It does not guarantee
testing of core logic. So you could have high
code coverage and still not test
your core logic of the application because
or logic has more to do with how you write your test than having
tests written. To resume, let's go over
effective unit tests. They should be low
cost with high value. They could be automated, and they focus on the
essential part of the code.
4. Integration Testing: Hi there. Welcome to the
lesson in integration testing. So we talked about
unit testing, right? They are fast. They test
the unit of application. However, you cannot
be sure your system works if you rely
only on unit tests. There might be a necessity to validate how
different parts of the software integrates with each
other and external systems, such as the database,
the message bus, emailing systems, et cetera. Integration testing
helps validate how components work in integration
with external systems. When you think about
integration testing, it's important to maintain a balance between unit
and integration tests. Integration tests validates integrations with
external systems. Therefore, integration
tests tend to be more expensive to
maintain than unit tests. These tests typically
runs more code than unit tests because it runs
through external dependencies. This improves protection
against regressions. More than unit tests. Regression testing refers to a software testing
technique that re runs non functional and functional
tests to ensure that a software application works as intended after any code changes, updates, revisions,
improvements, optimizations, et cetera, are done. The number of tests between
integration and unit can drastically differ
from application to application
depending on the needs. So let's talk about when to
apply integration testing. Integration testing validates
how software integrates with external systems
and dependencies. And when it comes to
these dependencies, we analyze two types. The first is control
dependencies, where you have full control, like data access methods
to the database. The second are
uncontrolled dependencies, where you don't
have full control, like a service bus library. What if I can't access
control dependencies? So let's say that for
security reasons, you don't have access
to a real database for your control dependencies. In that case, you
should avoid writing integration tests because
it provides low value. Tests need to provide value. Conclude. Integration
test validates how components work integration
with external systems. They cover the presentation
layer such as controllers, and they best protect
against regression. Even though we are covering
this at a very high level, integration tests
are very important. They are not the
goal of this course, since we'll be focusing
more unit tests. However, understanding the
types of tests available is crucial when it comes to
planning for testing. Yes.
5. End to end Tests: Hey there. Welcome to the
lesson on intend tests. Into end tests consist
of tests that cover application workflow that is usually done by a
person or a program. This tests helps validate systems where all its components are taken into consideration, such as the database, service bus, user interface,
and other integrations. The user interface, such as the platform or
browser navigation, and back end processing are
also covered on ten tests. Enten test covers functionality of the entire software
application with real world end user simulation and replication of factual data. At this point, the code or
database is irrelevant. The idea is to provide an
input to the system and expect an output through the same process
as the end user. Into end tests are realistic. Therefore, doing
this type of test, you should use a platform in which the system
is running on. You could use a web browser
to verify a web application. You could use an
iPhone to validate an IOS application or Windows to test a
desktop application. The idea is that you're using whatever the end
user will be using. Right? So if you think of a car, you have a motor
and you can test your motor or parts
of the motor, that would be your unit test. If you want to test
how the motor works with other parts of the car, that would be your
integration test. And then your ten test
would actually be getting into the car and
driving the vehicle. Let's look at some
of the advantages. First, tests are more realistic. Methods are not
tested in isolation. Instead, you simulate
the testing from the desired platform
with inputs and outputs. They are reliable
for regression. The test could go
through all layers of the application, starting
with the front end. And they also give you
a confidence boost. For quality assurance team, end twin tests can be used as a final step before releasing go to production with confidence that existing
functionality did not break. Let's look at some of
the disadvantages. First, performance. System tests are often slow
compared to unit test. Imagine everything a system
test or ten test has to do, including starting and running the entire system with
all its components. The test also has
to interact with real application and actions may take a few seconds
or even minutes. Second, environment setup. Tests are also harder to write. Some of the components such
as the database may require a complex setup before they can be used in a
testing scenario. Think of connecting
authentication and making sure the database has all the
data required by test case. Additional code is required
just to automate the test. The behavior variation. Sometimes the same
test might have different behavior results
depending on the integrations. Imagine a system test
that exercise a web app. After the tester
clicks a button, a HTTP request to the web app takes half a
second longer than usual. Due to that small variation
that we often don't control, the test can fail, right? The test is executed again, and the web app takes
the usual time to respond, and the test passes. Many uncertainties in
the system test can lead to an unexpected behavior.
6. The Testing Pyramid: Hey, there. Welcome to the
lesson on the testing pyramid. When working with testing, it
is important to first have a clear understanding of the different test levels,
their cons and pros. Therefore, your team can invest on whatever
makes more sense to the objective of the organization
or projects in hand. The following pyramid contains
all levels of testing. If you research testing Pyramid, you should find a couple
of variations of them. First, we have unit
testing at the bottom. Well, all major business
rules can be tested. Second, we have integration testing where you tend to have more complexity due to the integrations with
external services, and they tend to be written for less scenarios than unit tests. Finally, we have to
end tests at the top. It covers functionality of the entire software application
and tend to be more complex to write and maintain due to the possible changes
in the application. As seen in the
figure, the bottom of the pyramid is less
realistic and less complex, tend to be easier to maintain. And as you go up the pyramid, complexity and
maintainability rises. Unit tests are easy to
write. They are fast. They combine with
developers expertise and could be released along
with production code. For integration test, the focus is to test the integrations, and that might not apply to
a huge section of the code. There might be
specific scenarios where it makes more sense
for integration tests. Finally, tu en tests can be
more complex to maintain, especially if they
are automated. Many organizations have
a specialized team just for this purpose
such as Kiwi. The focus of this
course will be on unit tests. So let's summarize. The objective of
software testing is about finding the
presence of bugs. Even though there are
multiple types of tests, testing everything
on most systems is not viable or possible. Finally, we have test levels. There are different levels of testing from small unit tests to test that integrates
with external services and each provide their
own unique value. So it is important for you as a software engineer, QA analyst, or somebody involved
in a software project to understand each
of these test types, their level, and when to apply. I invite you to join me in
this journey to go deeper, learn new skills, or
update existing skills. The goal is for
you to understand, implement and apply unit testing to your software application. If you want to grow as
a software engineer, quality assurance analyst,
or learn about testing, this pathway is right for you.
7. Case Study Presentation: Hey, there. Welcome to the lesson on a
study presentation. So let's talk about the
Cesar Scholarships K study. Cesar College gives
a scholarship to 50% of their applicants based on their high school
grade point average. After the application
deadline ends, the submissions are evaluated and half of the
students are awarded. A list of the awarded students is sent to the
responsible sector. Now let's think how we would
implement this in code. First, we would create a method called calculate
awarded applicants. The method would
receive a list of students and return the eligible students
for the scholarship. The input is the list
of student names in their respective
grade point average. The output is the list
of the awarded students. Here is a code snippet of the method calculate
awarded applicants. The first part of the code is
using a.net library called Link to order by the applicants in the list
by the grade point average. The second part of the
code is keeping half of the elements on the list and
returning the second half. Since the list of applicants is ordered from
least to greatest, this implementation
should return half of the students with
the highest GPA. Now the developer runs his application and
does one test case, and it works, as you
can see in this figure. We have Kyle and Luena
with the highest GPA, and the program returns the
correct awarded students. At this point, as a developer, you are happy with the result and deploy your
code to production. You are working on a small team and there is no Kiwi members, something that is very typical
in some organizations. However, a couple
of hours later, you get a call from the client saying that the application
does not work as expected. On the next lesson,
we will review what happened and how unit testing can help prevent these issues.
8. Applying unit tests: Here, welcome to the lesson on applied unit test
to a given use case. So let's go over the calculate
awarded applicants method. Here's the code nippet
for the method. And let's go over this
logic really quickly. The first part basically orders the students by
the grade point average. The second part is kipping half the elements and
returning the second half. Since the list is ordered, we return the second half of the students with
the highest GPA. So here we have an example of a unit test
written for that method. For now, don't worry about the naming convention or
how this was created. The goal of this test
is to validate that the applicant list is empty when no applicants
are submitted. When we have a oh eight pattern that we will go over
in the next module, we have the arrange, the act
and assert, as you can see. For this test, we
arrange an empty list. We call the method, calculate
awarded applicants, and store that in
the result variable. Last, we validate or assert
if the result is empty, which is the expected result. So the test passed in RNR code without having to
run the actual application. Here we have a second unit test. The goal of this test
is to return half of the applicants based on
the submitted applicants. We arrange the list of submitted applicants and store that in an actual variable. Then we create a list of the expected applicants and store that in the
expected variable. The method is then called and the applicants are stored
in the result variable. We then validate that
the expected applicants are the same as the actual. The test passes and we now feel more confident that the code
is working as expected. Here we have our third unit test that validates one applicant. After writing the
third unit test, you start to
question edge cases. If two candidates apply, should we return only one? We question the product owners, and they say that we should have a minimum of ten
students awarded. After the requirements
are updated, we run the tests again
for ten applicants, expecting those ten
applicants to be returned. Basically, we work
our test to fail, but to follow a
new business rule. We run the following test, it fails because it returns
half of the applicants. We now implement a
change in the code. Here we check the
number of applicants. If the total applicants
are less than 11, we return all applicants. Then we run the unit test
again and it passes. However, we are still
missing something. Can you think of
what is missing? What if 12 people apply? The programmer returns six. I'll let you figure
out how to solve that. The goal here is to show how Unitest can help you validate the business logic
of your application and make changes to your
code with more confidence. Many times I see that the
error developers make are on small details that Unitest can help and should help avoid. When writing unit tests, it helps you think
more systematically. If we start analyzing
other cases, we can think of things like,
what if all GPAs are equal? What if the GPA
is less than one? With the unit testing strategy, you can make changes to
your code without having to worry about breaking
existing functionality. Once you have tests
that cover those cases, it should be a
confident booster for all devs and they bring more value to the team and to deliver high
quality projects. The code for this lesson will
be attached to the course.
9. Getting started with Xunit: Welcome to the lecture on
Getting Started with XUnits. So Xnit is a free, open source, community focused
unit testing tool for.net. Written by the original
inventor of N Unit Version two, Xnit is the latest technology for unit testing in C sharp, FSAP, visobsic.net, and
other.net languages. X Unit works with R
Sharper Cold Rush, Test driven.net, Xamarin,
and other.net type projects. It's part of the.net Foundation, and it is currently
licensed under Apache two. So why should you use Xnit? Well, it's well used by the community and proven
to be a great library. As you can see, there's more
than 450 million downloads on Nugget to this date. It received constant updates and improvements on
a monthly basis, sometimes more than
one time a month. It's a well designed
library based on a very robust library
called In Unit, and its current version is 2.80. If you're watching this video, I'm pretty sure that this
version is already updated, or the 2.80 is not
the latest version. Well, from now on, most of the examples will be
implemented on Live Code. I will implement the examples on this module using Visual
Studio Professional 2022. However, if you're
using Windows, I recommend the free
version of Visual Studio. That is the community version, and it has all the functionalities
that we'll be using or that I'll be using within
Visual Studio 2022. You can download Visualstudio on visualstudio.miicrosoft.com. If you're on a Mac or Linux, you can download
Visual Studio Code. Alright, so let's get started with some basic implementation. To get started, first, you're going to
open Visual Studio and create a new blank project. I already have my blank
project created here. Basically, it's just a template where we can insert
new projects. If you want to create
a new blank project, just click on File New Project
and select Blank solution, and that's basically it. Once you have your
Blank solution created, or you can use the existing
one from the last lecture, and I put the download link
as part of the lecture. So you can download the
existing one or you can create one from scratch
just as I'm creating here. So what you're going to do is
you're going to right click the solution, add new project. We're going to go ahead
and select Class Library. If you don't see it here, you can kind of search for it here. Class library. In our case, we're focused on the C sharp, make sure you see the C sharp
here and click on next. This library is
basically a new layer. This layer is going to hold the business logic for the tests that we're
going to create. Let's go ahead and name
this business dot Caesar. You can name it
whatever you want. I do recommend the prefix
business just for context. Next, we're going to go
ahead and choose Det eight. It really doesn't matter
the.net version here. If it's standard or seven or
eight or even a nine or ten, whatever version you see, since we're just going to implement
some basic functionality. So I'm going to go ahead
and choose Det eight and create and this will create
a new business layer, or a new class library that we represent as a
business layer. So now let's go ahead and
implement some logic. So let's rename this. So I'm going to
right click Rename the class one to calculator. And I'm going to go ahead
and add a add method. So I'm just going to
paste in add method. So this is a ad. It
returns A plus B, and it has the
parameters A and B. So we have our
business layer and we have our calculator class. Very simple implementation.
I can right click here, click on Build just to make sure we don't
have any errors. Build succeeded, and
that's basically it. Now, let's go ahead and right
click the solution again, click on Add New project. Let's go ahead and add a
new test layer, right? So let's go ahead and select
a X unit test project. So the same thing
here if you there should be like a test
yeah, here it is. There is a test section here, so I'm looking for C Sharp. I can even choose
all platforms test. And as you can see,
there's MS test, N unit, unit test or unit test project
for the net framework. And at this moment, we
are focused on X Unit. But I just want to
show that, you know, there's a lot of pretty
nice test projects here. So let's go ahead and focus
our mission here on X units. So I'm going to choose the X
unit template for C Sharp, I don't want to Det framework, and I'm on Det eight.
Click on next. I'm going to name this
Caesar dot business because we are testing
the business layer. Actually, let's go ahead
and name this business. I think I accidentally inverted, but that's fine, business
dot Cesar dot test. I usually I will
usually do something like Cesar dot business,
but that's fine. The important thing here
is that we understand that this test project is testing the business dot Cesar project. So as you can see, the prefix
is business dot Cesar. And then the suffix is test. So this is a test
project that is testing the business
dot Caesar layer. So I like to keep
it very explicit. When I click Create, we see two projects within
our solution. We have the business dot Caesar, where we have our business rules and the business
dot Cesar dot test, where we have our unit tests
for the business rules. As you can see, if I
click the project, you'll see the target framework. You'll see a couple of libraries that are within this project. So you can see the
target framework that specifies the framework
of the project. You can see implicit using notable the packages
that it includes. So it includes the X units, which is basically the package that contains the
testing framework. You have the X Unit
runner dot Visual Studio and the miicrosoft.net
dot test dot sDK. Basically, they are
required for you to run your test project
within Visual Studio. You also have other libraries
like the covered collector, which allows you to collect code coverage if that's
what you tend to do. Now let's go ahead and
end the lesson, right? We created our project. We created our test project. We can right click the
solution and build. Everything is building
successfully. And then on the next lesson, we will write some unit test. And also this project, you can download
this template from the download section
of the course.
10. Working with Test Explorer: Hey there. Welcome to the lesson in working
with the Test Explorer. On the last lecture, we
created our test and we created our test project and we created our business
dot Caesar project. Within our business dot Caesar, we have a basic calculator, and then within
the test project, if you create a test project, it automatically creates
a unit test class. Here we have a fact
attribute which represents a test method and this test method
is called test one. We have a test class
and a test method. Now we're going to
explore this test method. Let's go ahead and click on the test tab here and then
click on the Test Explorer. The test explorer is the name of the window that lets
you browse and run your tests or unit tests
within Visual Studio, right? You have kind of
some groups here, like this first group, it allows you to run your tests, so I can run all tests. I can run selected tests, so I can actually select the
test that I want to run. In this case, there's only one, and I can run that single test. I can repeat the last run, and then I can also
run my failed test. Second tab here or
second section here, you can kind of filter
your tests, right? So you have the total
tests. You can filter. All of them, I can filter,
the passing tests. So if I click here, since
there is no passing test, since I haven't ran anything, it just doesn't show anything. I can filter the failing test and I can filter the
tests that were not ran. So if I click here,
you can see that this test was not ran yet, so it's this plane here
within Test Explorer. Then the last section here is a place where you can kind
of configure your test. You have advanced
options like changing the processor architecture and automatically running your
tests after every test build, you have the options,
some options here. So this is just to kind of configure how you want
the tool to behave, and you can explore this if
you like, some customization. Now, let's go ahead and
write some tests, right? So first, I'm going
to run this test, so let's go ahead and run it. Click on Run. And as
you can see here, let me just click here so I
can show all of the tests. You can see that
it passed, right? So a test will fail when
it explicitly fails. Even though there's
nothing written to it, it does not explicitly
fail, so it's passing. So let's go ahead and write
some actual test, right? At this point, we're not worried about formatting our test. There's a lesson just for that. So I'm just going to
copy some code here that represents a passing test
that adds two numbers, right? So I'm going to call
this passing test. And I'm going to use the assert. The assert is basically
checking for equality. I'm checking if
you explore this, you have an expected
and an actual value. So I expect four, but my actual value is
this because this is what I'm calculating within my calculator class, right? So for the calculator
class to be used here, we have to reference our
business dot test project. So let's go ahead
and right click the dependency, add
project reference, and click on
business dot Caesar, so we can add our calculator
reference to this project. There you go. You can now calculate two plus two and the expected value is
four if we run the test. I passes. You can also
debug, which is very useful. So if I right click
the passing test, you can also right
click the class here and either one of those
and just click on Debug. You see it hit my breakpoint, I can kind of step into
this as much as I want to. So here I have A plus B, it returns, and then it
asserts, and it passes. So that's debugging unit test is a pretty useful functionality. Let me go ahead and paste
some more code here. So I went and paste
the failing test. So this is basically
the same thing, right? We're adding two numbers, but we expect five. So let's go ahead and
fail this on purpose. Let me run all tests. And as you can see
here, there's a red X. So now my tests are failing
because one test fails, kind of like all the
name space fails, right? So here, if I click
on the failing test, I can see kind of why
it failed, right? So you can see that we're using the assert and the
values differ. The expected was five, but the actual value was four. As you can see,
we're trying to add two plus two, and it fails. So here you can kind of explore, right, or visualize how test explorer is
very useful, right? You have the summary of
why a failed test failed. You also have a summary
of the passing test, like the duration, et cetera. So this is a very useful tool that we're going to be using
throughout the course, and it's highly used by developers that works
within Visual Studio.
11. Improving the redability of your tests: Hey there, welcome to the lesson on improving
test readability. So let's analyze
the class that was created when we created our
first unit test project. When we look at the
unit test one class, we will see the fact attribute. This attribute is used to mark
a method as a test method. It signifies that a method represents a fact that
should be always true. A test marked with the fact attribute represents
a single test case. If the test method throws an exception or
fails in assertion, the test is considered a
failure or a failed test. Well, one of the most
important things about testing is how to name your tests. The name of your tests
when it comes to test methods should
consist of three parts. The name of the method
that is being tested, the scenario under which test or under which
it's being tested, and the expected behavior when
the scenario is involved. And why is naming your
test so important? Naming standards are
important because they explicitly express the
intent of the test. Cest are more than just
making sure your code works. They also provide documentation. Just by looking at the
suite of unit test, you should be able to
infer the behavior of your code without even
looking at the code itself. Additionally, when a test fail, you can see exactly
which scenario don't meet the expectations. We also have another
attribute called theory. The theory attribute is used to define a parameterized test. It allows testing
multiple inputs against the same test logic. You can provide one or
more data structure via attributes like inline data to supply the test with different
input values. Each set of input values is treated as a
separate test case. If any of the test case fail, the entire theory
is considered fail. The following image displays an example of a theory
attribute in use. We will also implement
this ourselves. A very common pattern
is the AA pattern, arrange, act, and assert. This pattern has almost become a standard
across the industry. It suggests that you should divide your test method
into three sections, arranged, act, and assert. Each one of them only responsible for the part in
which they are named after. Since readability is one of the most important aspects
when writing test, separating each of these
actions within the test clearly highlight
the dependencies required to call your code, how your code is being called, and what you're
trying to assert. While it might be
possible to combine some steps and reduce
the size of your test, the primary goal is to make the test as
readable as possible. Now let's look at some
examples in code. Cool. So here we have our
unit set solution, and we have our
Unit Test one with the failing and the
passing test, right? So the first thing
we're going to do is we're going to
rename our class. So let's go ahead and rename
the class to calculator. Test. The reason we renamed the class to calculator test is because the class that is
being tested is called calculator,
as you can see here. So when we have a
calculator test class, this infers that calculator test is testing the calculator class within the business
library class library. Go ahead and rename our tests. Just to recap, the name of the test should contain the name of the
method being tested, the scenario under
which is being tested, and the expected behavior. So I'm going to go ahead and
rename this and call it add, which is the name
of the method under line to separate the next part. And let's just say should add. Underline and the
behavior of the scenario. So let's say when integer. Cool. When we read this, we can see that
we're trying to add, I should add, and we know that
the number is an integer. Cool. Now we can explore some
more testing, right? So let's go ahead and
copy this pastelss here. Let me rename this to a theory. And let me just put
this on por role. Let's go ahead and
add some inline data. Now that I have the inline data, I'm going to delete
the failing test. I'm going to create
my parameter. So in A, and B and the expected. Let's go ahead and
change this to A, actually expected, and
we're adding A and B. So let's say two plus or
two plus two is four. Four plus four is eight and
eight plus eight is 16. Let me go ahead and also
separate this a little bit. Let's put what we expect
here for the result. This calculator add and then
we'll just replace this. Now the result would be my act. And this would be my assert. So we have A here, right? So we're missing the arrange. Let's go ahead and arrange
the calculator class. I'm going to save our A new calculator instead of creating an instance. There you go. Arrange. We're
arranging a new instance. We're acting on the results, and then we're asserting what is expected and based on
the result, right? So let me go ahead
and comment this guy. And let's go ahead and
look at our test explorer. And run our tests. As you can see here, there are three tests and all of
them are passing, right? We have one method that
represents three tests. So just to review
here, the arrange, it basically sets up testing objects and prepare the prerequisites for your test. The act performs the
actual work of the test, and the assert
verifies the result. So this clearly
separates what is being tested from the setup
and verification steps. Okay.
12. More on Assertions: Hey, guys. Welcome to the
Lesson on Assertions. So here I've created a
class called helper, and this class
contains a G model, which returns a new instance of the helper model with
the property's name, date of birth, bank balance, and favorite food collection. As you can see, our model
is within the same file. Also have a method
called process helper, which depending on the value, it returns an exception. The value is no, returns this. If the value is one, it returns a non
implemented exception. If the value is two, it
returns an exception. So with this class or this helper class
is going to actually help us kind of go over
the assertion methods. So within the business
dot czar dot test, we have a helper dot test test class which
we're going to go over. Let's go over or start with
the Boolean assertion. The Boolean assertion
is actually checks if the assert
is true or false. So you have assert dot true and you also have assert dot false. What this is doing,
this is actually instantiating a
new helper model, and then we have the
expected bank balance. The result is actually
getting a model. And within this result,
we have a bank balance. So we're checking if the bank
balance is equal to 150, if the bank balance
is equal to 150, then this will be true
and this will pass. If I run this, you
can see it passes. If I change the
expected bank balance to 200 and I run it again, it should fail. And
there you go it fails. Cool. So let's go
ahead and move on to the string equality assertion. Now, the equality
assertion is kind of it tests for equality with
expected and actual value. And this is one of the most
used methods for assertion. The Boolean assertion could actually be replaced
by this one, and it's often ignored, right? Since the Boolean assertion kind of provides more information
of why it fails. So here we have
an expected name, an expected first name, and an expected last name. We get the result
from the get model, and we're checking if the name
of that model is equal to the first name if it starts with the expected first name and if it ends with the
expected last name. I I run this, string equality, it's down here, it passes. So if I look at my get model, you can see that the
name is Cayo Susa, and if I look at
my expected name, it's Kyosusa, it starts
with yo and ends with SSA. So equal starts with and ends with is some of our
equality assertions. So let's go ahead and
move on to the next one. So the next one is
numeric assertion. So for numeric assertion, we have the assert.in range, and there's also the assert
dot not in range, right? So here we're checking if the bank balance
is 10-150, right? So if I run this, you can see that it passes because
the bank balance is 150. So if we look at
this one that fails, we're checking if it's 10-149. And if I click here, you can see that value is not in range. The range is ten to 149, and the actual value is 150. So on the test detail summary, it can really help us
understand what happens. So let's go ahead and
move on to the next one. So the next one is the
reference assertion. So the reference assertion
kind of asserts, you know, we have
the assert dot same. There's also the
assert dot not same. We have the assert dot no, assert dot N no. I'll assume that the same in the null is the mostly used one. So the same asserts that the expected and actual
objects references are the same object, right? So here we have a result that
is getting the get model, and we have the helper model, getting the result value. So these objects
are actually result and result and helper model
are the same object, right? So if I run this, you'll see
a reference assertion past. However, this can get a
little confusing, right? We have the reference
assertion failure. So here we have the result. And then I created like
a helper model here, which contains the same kind
of the same type object, however, with the same values, but it's a new instance. So the helper model contains
the same value name, same value, date of
birth, same value, bank balance, and favorite
food collection as results. So it's kind of like
equal in that sense. However, it's not
the same object since this is a new instance. If I try to check for if they are the
same, it actually fails. Reference assertion fails. And then for our next assertion, we have the type assertion. For the type assertion, we're actually checking
at times may want to assert that the object is exactly the type
you expect, right? So here, the get model is
actually of type helper model. So if I change this
to helper and I run, we would expect a failure here. There you go. It fails, right? It's not the exact type. If we change it back
or roll this back to helper model and run
this should pass. And there you go. It passes
because it's the same type. Result is of type helper model. Cool. So let's go ahead and
move on to the collections. So collections are also highly used within a
lot of applications. So this can be really helpful. So you can assert if a
collection is empty. So our favorite food collection here where it
actually has value. So if I comment this out and I run this, this should fail. Collection empty assertion, it fails because there's
actually rice, beans, and eggs as part
of the collection. It's not empty. If
I uncommon this because this is kind
of forcing it, right? It's forcing the
favorite food collection to an empty collection. This now will actually
pass. There we go. And then we also
have the not empty. And if we run the same
favorite food collection, we know there's rice in there, so it's not going to fail. There's the collection
contains assertion. So this contains is
actually checking that an expected item is
found within that collection. So here, there's a couple
of ways we can do this. I just use a fact,
but you could use the theory or done it
in a different way. However, let's just look
at it how it is, right? So here we're checking if the favorite food collection
contains rice, beans or eggs, and eggs,
in this case, right? Rice and beans and eggs, and it does contain if
I change this to egg, it should fail because
it does not contain egg. You know, it contains eggs. Cool. And there's also, like, does not contain, so it does not
contain any carrot. So here, it does not contain and it passes because
it does not contain arag. For our last analysis, we're going to look at the
exception assertion, right? And for the exception assertion, we're actually using
the action delegate just to separate the arrange from the act from the assert. So here we have the act is actually the
delegate is actually, it calls or sets
the process helper, and then on the assertion,
it actually acts. Here we're kind of expecting here an argument null exception. If we look at the
process helper, when the value is null, it throws an argument
Dull exception. So if I run this, Argument
looks. There it is. So if I change, let's go
ahead and change this to not implement not
implement exception. If I change this to not
implement exception and run it, it fails because it does not throw in not
implemented exception. It throws an argument
no exception. So this is also pretty
useful and nice.
13. Fluent Assertions: Hey there. Welcome to the lesson
on fluid assertions. So fluent assertions helps
you write assertions in unit test that are both
simple and expressive. The library contains a
set of extension methods that can be used to specify the desired
outcome of a unit test. It helps the assertion kind
of look a lot more natural, and it can also be
easier to comprehend. And it also keeps your
code clean and simple. You can check out some
more documentation on fluid assertions.com. As of 2024, there are more than 446 million downloads of the fluent assertion
library inNuget. The version of this
date is seven.00. This version can
change depending on the time you're
watching this video. So let's go ahead and look
at how we can implement fluent assertion based on
kind of our last um, video. So our last video, we talked
about helper tests, right? We went over all
of the or most of the assertions within
the X Unit library. And in this video, we're going to basically update the library or include the
fluent assertion to assert. So first thing you're going
to do is right click package the packages on your
business dot czar lat test, manageNugt packages. Here you can click on Browse
and just type fluent. You'll see fluent assertions, and you can just download
fluent assertions. As you can see, the current
latest stable version is 70 dot zero. So here we've downloaded
fluent assertions, and once you download it, you can just kind of
force it or use it here. So using fluent
assertions, and that's it. We can now use fluent
assertions within our project. So I'm going to be doing
some copy and pasting here just to speed up
some of the typing. So here, instead of saying
assertion dot true, we're going to go
ahead and say result, which is the object that
we're trying to assert. And then bank balance, which is that property, should
be expected bank balance. So if you compare assert dot true result bank balance equals expected bank balance, result dot bank balance
should be expected bank balance sounds
a lot more fluent, at least it does, to me, right? It's a lot more readable
and could you know, pretty easy to
understand, right? So if I run this, you can see that it fails because I think we've changed this
on the last lecture. So if I change this to 150, here you can see that it fails. It actually provides a
lot of details here, see, 200, but not but found 150 difference of 50,
so that's pretty nice. If we run this now, that I change it
to 150, it passes. So let me just close
that out. Cool. So let's look at our shring
equality assertion, right? So here we have kind of
like a three liner here, and you can actually make this. It's still a three liner, but it's a lot more fluent. So here we're checking
for equality, starts with and
ends with, right? So we're checking for the name. So result dot name
should start with expected first name and end with expected last name and
contain the expected name. So a lot more fluent, a lot more readable from
my point of view. And if we run this,
we should get a passing test for
the string equality. There it is. String
equality, passing. So let's go ahead and move
on to the numeric assertion. So numeric assertion here we're actually checking
for the range. So here, very similar to the previous test result
bank balance should. So this should is kind of like it's going
to be everywhere, and be in range of
low and high range. So if I comment this out, we should see that this
is going to work. See, there it is, it works. And then this one I can
just copy and paste. I believe it's the
same thing. This one is failing because it's
not within the range. So let's see what happens. Numeric assertion
failure, but found 150. Yep. So that's kind of like the numeric assertion failure. So let's go ahead and
look at our next one, which is the
reference assertion. So same thing here. The result should be same
as the helper model. In this case, it should fail because this is a different
instance of result. So this is failure.
There it is. It fails. And I believe this
should be the same. So this one actually
passes because these are the same objects. So
let's look at this. Result should be should be null, see, you can see,
should not be null. So I could say, Hey,
result should not be null and should be same as. So I can comment these two. Kind of run this,
see what happens. There you go. Reference
assertion passes. So let's go ahead and move
on to our type assertion. So for the type assertion, basically, very similar
to the previous ones. So results should be should
be of type helper model. And this is the
type there it is. And let's go ahead and
look at the empty. Oh. Result favorite food
collection should be empty. And then let's go ahead
and look at the not empty. So should not be or search for empty should
not be empty. There it is. Not be empty, should
not be empty. So It's coming out these guys. And let's run this one. So this one's not empty. And then there's this one
that is empty. There it is. They both pass. And then we have the collection contained. So this one is a
little bit different because this one
actually uses a list. So it does simplify it a lot. So instead of using variables,
it just uses a list. So here we have this
list within a collection within that the
expected variable. We don't need a
multiple contains. Now we can just use the contain. So here I can comment this out
and I could just run this. I'm checking if the
fare food collection contains the expected list. And as you can see, it passes. So let's go ahead and move on to the So this one's contained. The other one is
just not contained. So basically, same thing here, results should be like a not
contained. Not contained. And there is where is it? Collection does not
contain assertion. Collection does not
contain assertion, and for our last one
is the Argument null. So for the argument no, it's basically Act should
throw Argument null exception. Does. And there is there's our fluent assertion
library implemented.
14. Code Coverage and Tips: Hey there. Welcome to the
lesson on cold coverage and tips. So let's recap. What is code coverage? In software engineering,
code coverage, also called test coverage is a percentage measure
of the degree to which the source code of a program is executed when a particular
test suite is ran. Many developers say that
code coverage is not very useful when it comes to
testing, what matters, right? So maybe if you want to test the business logic, et cetera. However, code
coverage should not be used as a percentage goal. Code coverage tools should
be used to enable developers understanding of what parts
are not covered and why. So first, we have the
line coverage, right? So developers who aim to achieve line coverage on at
least one test case that covers the line under test. It does not matter if that line contains a complex
if statement for, you know, full of conditions. If a test touches
that line in any way, the developer can count
that line as covered. We also have branch coverage, which takes into consideration
the fact that, you know, the branching instructions
like the ifs, the fours, the whiles and so on. So it makes the
program, you know, those instructions
makes the program behave in different
ways depending how the instruction
is evaluated. For sample, if A, if statement, you could
have A and B, A or B, having at least a test case for the true and false condition is enough to consider that
the branch is covered. And then we also have the
Fine code coverage extension. So this extension supports.net core projects and.net
framework projects. And it's an extension
that you could use within the Visual
Studio community edition. It's free, and it helps
us understand and evaluate the code coverage
for our application. So let's go ahead and
look at the code from the previous lesson
and see how we can use fine code coverage to
measure the code coverage. So here we have the project
or the unit testing solution. So the first thing
you want to do is install fine code coverage. For that, you're going
to click on extensions, manage extensions, and
then you're going to browse for fine code coverage. There is fine code coverage. In this case, it's
already installed, but you can just
click on Install. I'll click this
Color TweakerP here. You see there's an
install button. So if you don't
have it installed, there should be an
install button, and it kind of
installs it for you. Here we have a screenshot
of how it looks, right? So let's go ahead
and run this tool. So to run the tool, you're going to go ahead and click on View. Check for other windows, and then you should see the
fine code coverage here. So to actually have
this output here, we have to run our tests. So let's go ahead
and run our tests. 13 passes to failed. The two that are
failing is the one that we kind of expected it to fail. But let's kind of see what
we're trying to look for here. So within our source code, we have the helper model. We have the helper, and we
have the calculator class. And then you can
see that there's 100% coverage of the
calculator class. There's 70.5% coverage of the helper class and 100% line coverage
of the helper model. We are missing tests
within the helper. If we look at the
helper class here, let me disclose some of this. We can see that it is
red here, you know? See, these were not tested. So that's why there's 70%. And what is in green
is what was tested. So here, there's, like, a yellow because this is a
branch coverage, and then red is stating
that it was not tested. So I can kind of
see, okay, these are the lines that I
need to test, right? These are the lines
that are missing, and this is a branch that
I might want to consider. It's a pretty nice tool. It helps you
understand, you know, what has been covered,
what hasn't been covered. You know, you can
check the results. You can check for branch, check for line coverage, check the percentages, and it's a really awesome tool.
So there's the coverage. There's like a summary, there's kind of some hotspots, if you want to look at some like cyclomatic complexity here
and some coverage logs. And that's basically it.
15. Unit Test with xUnit Review: Hi there. Welcome to the lesson on unit testing
with X Unit Review. So now that we've written
our first unit test, we explored the X unit library. I want to incentivize
you to write a software that simulates
a calculator operation. Within the software, we
want to make sure we have the basic operations such as
add, divide and multiply. You should write unit tests that covers all functionality, passing and failing cases. After that, you want
to make sure you run a code coverage tool so you can measure code coverage
for your code base. Then I incentivize you to
post your results on GitHub. So let's summarize this section. We started with getting
started with X Unit. I explain why Xnit is such
an important library. We created a testing project
and explored Visual Studio. We wrote our first unit test. We viewed the unit test using Test Explorer and ran it
through the Test Explorer. We formatted our tests
using thea pattern, which is a highly used standard. We also analyzed cold
coverage and some basic tips. Within this lesson,
you should be able to comfortably
write unit tests, run cold coverage, and have basic formatting
for your tests.
16. Social Security Numbers Requirements: Hey there. Welcome to the lesson on Social Security
numbers requirements. So let's talk about
software requirements. Software requirements are
a very valuable artifact when it comes to
software testing. Requirements contains the feature or non
functional constraints that the software
must provide to fulfill the needs of users
and other stakeholders. Requirements tell us
precisely what the software needs to do and what
it should not do. They describe the intricacies of the business rules
that the software must implement and how
they should be valid. Therefore,
requirements should be the first artifact
developers take into consideration for when
it comes to testing. We will build a test
suit using X Unit in Visual Studio based on
the given requirements. We're going to create
an application that inserts a user to a database. We're going to use
the following data, the user first name, the user last name, the
user date of birth, and the user's Social
Security number. Users between the age of ten and 80 are allowed
in the system, and the Social Security
number must be valid. The part about the database, we're not going to implement
the actual insert, but we will implement what would be a mock of an insertion
of the database. And you'll see that
once we start coding. For the Social Security
number requirements, these are the requirements. First, the Social
Security number should follow the
following format. The first part is
called the area number. The second part is
called the group number, and the last part is
called the serial number. The area number contains
three digits and cannot be 000666 or 900-999. The group number may not be 00, and it's also a
two digit number, and the serial number is a four digit number
and may not be 0000. Cool. Let's look at some code. The first thing I did
here is I created a Social Security Number folder on the business dot cesar class. For that, you can right
click the project, click Add and then New folder, and then just name that
folder whatever you want. In my case, I named it SSN, that stands for Social
Security number. Now we're going to go ahead
and click the folder, click on AD and add a new class. I'm going to name
this class user. Right, so now I'm going to
create a model for the user, so I'm just going
to have it public and I'm going to create
some properties. So first, I'm going to
create the first name. Let's have the last name. Let's have the date t date of birth.Hom. And let's go ahead and have
the Social Security number. So we have the first name
String, last name String, date of birth, as datetime and Social Security
number as string. Cool. So that is our user entity that we're going to
be working with. Now I'm going to create
a user data class, which is responsible for inserting the user
onto the database. So let's go ahead and create
a class called user data. I'm going to create make it public, make
everything public. So let's go ahead and name or create a method
called insert user. So public void Insert user. I was changing my
keyword settings. All right, so public
void insert user, and the parameter of this
is actually the user. Cool. So we have a user
as a parameter, and we don't really care
about the database, so I'm just going to say throw
out implement exception. We're simulating an insertion, but we don't really care
about the database. Remember, that unit testing, we don't care about
integrations. So a database is an integration
to external system, right, that holds data, and we don't really
care about it. However, we do need
this method because we will be simulating an
insertion to the database. So we're mocking that, right? So we'll talk about this
on the next lessons, but let's just keep
it like this for now. So we have the user data. And last but not least, let's create a Social
Security number class. So I'm going to create
a new class and name it Social Security number. I'm going to also have
it as public. Cool. And I'm going to create a
method called insert user. So public void insert user. If we were developing this and some type of
structured architecture, we might have these classes
at a different layer. But at this point, I would
just keep it simple, right? So we have a method
to insert the user. I'm also going to create a
method of TableanPublic bowl, and I'm going to name it is valid SSN or Social
Security number, which basically is validating
the Social Security number. So social security
number, right? I'm just going to have this
for now just return false. And let's go ahead and
missing a There you go. Let's go ahead and
implement this method. So the or missing the user here. So the insert user
uses the user model. So if the or if not is valid, user dot Social Security number, or if the Social Security
number is not valid, we're going to go ahead
and throw a new exception. Let's just say invalid data
exception and I'm just going to try to get used
to this keyboard. All right. So cool. We have invalid data exception, so I'm just going to say
invalid SSN here. Right. Cool. Now we're going to
instantiate a new user data, which is my data method
responsible for inserting a user to the database just
to follow our requirements. So if the Social Security number is valid, I want to insert. So I'm going to
go ahead and say, Hey, insert my user
to the database. So now I have kind
of my skeleton of the application where
I have my entity. Model. I have my data model, and I have my business logic, where I implement
the validation and the insertion of
the, you know, user. So within this or
with this skeleton, we're going to implement,
all of our unit tests. And, of course, we're going to actually implement the
actual validations and Mc you'll see this on the next lessons as we implement and test
our requirements.
17. Mocking data acess with MOQ: Hey, there, welcome to the lesson on Mocking
data access with MC. So at this point, we
don't really care about inserting the user to
the actual database. We care about testing the
Social Security number rules. Even if the implementation for the user data
was ready and we had a database ready to go
at the unit test level, we don't care about
connecting to a database. Remember, this is not
integration testing. So the solution here is
actually to mock a user data. And to do that, we need to
create a data or create a user data interface and then inject that interface
within the code. And we're going to look
at that within the code. Um, so we have the MQ
package or mock package, MQ. It's a popular library
for mocking in.net. It's currently on version 4.272, and there's more than
730 million downloads. To recap, a mock object is created to simulate
the behavior of a real object in order to test the functionality of other software components
that depend on it. So in our case, we
want to kind of mock the insertion of the user
to the database, right? Um here we have kind of what our user data contract
is going to look like, so we're just going
to basically create an interface called I user data, and then we're going
to kind of use that interface and inject
it within our code. So we're also going
to use automok just to facilitate the
dependency injection, the resolve of the
dependency injection, and this goes in a
little bit deeper, but it basically
automates our mocking. So, currently Automoc has
15 million downloads. It's kind of a mocking
container for M, and it really helps if
you're invested in, like, inversion of control, and you want to decouple
your unit test, you know, changes on the
construction arguments. So let's go ahead and
Look at some code here. So here on the user data, if you click on user
data and click on control dot and
extract the interface, this will basically create an interface called I user data. For us, it automates
the interface creation, and then we can kind of use or inject the e user data within
the Social Security number. So I'm going to go ahead
and create a constructor. Here, I'm going to create
a private variable. I'm going to inject
this. There you go. User data, and then I'm going
to use this user data as my contract here instead of
instantiating a new class. So here and now I
have a contract, you know, user data interface. And also, now my
structure is coupled, in a sense, not coupled, but, you know, it has that
dependency to it. So Automa kind of helps us
resolve this dependency. It just magically
solves that for us. So let's go ahead and
close all of this, save it, close close and let's go ahead and look at our test. Here we're trying to test the Social Security
number class. Let's right click the project, click on AD and add a
Social Security number test to basically to
the test project. Let me rename this to social
security number test. We have the Social
Security number test. So let me go ahead and copy some code here just for
productivity purposes. So here we have Auto Marker. So I'm going to click on
packages Add Nugget package. I'm going to search for Mc, and I'm going to
go ahead and add the M or install the Mc library. Currently on version
four dot 20. The kind of clear
all these tests. I'm also going to
add the Mu automoc. So install both of
these libraries. These are the libraries that
we're going to be using. Cool. So we have MQ,
we have Mach Automoc. So if we go back to our test and if I kind of like
control that here, you can see that kind
of, Hey, use automoc. So here it's using Automoc now. And at the constructure of the Social Security number
test, instantiating, you know, or keeping
this object, kind of global right
to the auto Marker. So I have to recreate this every time or for every method. So here I have my automrkers
a global variable, and I'm going to go
ahead and create a method called Insert user. So I have this method here. Called insert user should not throw an exception
when user is inserted. So now I want to know, I want to make sure it doesn't
throw an exception, right? So remember, when
calling the user data, it actually throws an exception. If I go to the implementation, it's forcing the exception. We want to make sure this exception is not
going to be thrown. So for that, we're going
to first thing first, I'm going to create a private
method called create user, and this is going
to help us create users with a Social
Security number. So this method returns a user. It has as a parameter of
the Social Security number, it just returns an object
with the first name, last name, date of birth, and the given Social
Security number. So it's just kind of
like a creator for us. So this is going to create
that object for us, and I'm going to use this to arrange and set up our mocking. So let's look at our range here. Not this one, it's
this one here. Let's look at this arrange here. This arrange, we
have the user with valid social that we created
with the create user method. This is just
creating a user with a valid social
security number where we then instantiate a instance of the Social Security
number using our Auto MAC. Object. So this creates an instance, and it solves the
dependency for us. And then we kind of
set up our auto mock. So this our M, right? So every time a user
data is called, we want to make sure this is not going to throw
an exception, right? So we want to make sure
that when this is called, we actually mock
the insert user for this user with valid social. So this is going to
prevent mocking this will prevent an exception being thrown because we're not actually going to
call that method. We're going to mock the
answer user method. So now we can act, right? So remember, we're testing we want to make sure we
don't throw an exception, so we're going to
use a delegate here. So for this, we're going to act or call the answer user
as a delegate here, and this is going to help
us test for exceptions, as we've seen previously
on the exception. And for the
assertion, basically, we just want to make
sure that it does not throw an exception, right? So let's go ahead
and run this test. And it fails. It's throwing
a invalidated exception. Let's look at our
insert user method and it's actually returning an invalid Social
Security number. I'm going to force this to true so we can pass
this validation. Of course, we're
going to implement the code to validate the
Social Security number. At this point, I'm just going to make sure my test passes. I'm going to force
this to true for now, let's go ahead and re run this and should hit this
breakpoint here. So it did hit it did call this method and
hit all of this, and this is marked. This is bypassed and I successfully moked the
user data dot insert user, which is basically what I was looking for
within this lesson. We successfully mock this
and on the next lesson, we're going to go ahead and implement some of
our business logic.
18. Unit Testing the Requirements Part 1: Hi there. Welcome to the lesson on unit testing
the requirements Part one. In this lesson, we're
going to go over the requirements and
implement the age validation. So we implemented
the application that inserts a user
to a database, and we also implemented the model that we
insert to the database. So we have the first
name, the last name, the date of birth, and the
user Social Security number. In this lesson, we're going
to go ahead and implement the validation of the
age of this user. So users between the age of ten and 80 are allowed
in the system. So let's go ahead and
look at some code. So here we have
our code base with our unit test and
the create user. So first thing I'm going
to do is I'm going to create a new test to actually try to insert a user that
is younger than ten. So I'm going to go ahead
and paste this test. Over here so we can
go over it quickly. So here we have a method
called insert user, and it should throw an exception when age is younger than ten. Remember, the age range
has to be older than ten. So on the arranged section, I've implemented a variable that receives a date that is one
day from being ten, right? So you're 10-years-old plus one, that means you're nine days
and one day from being ten. Then we're going to create
a user based on that date. Let me go ahead
and base the code for creating the user
based on the date. This code here creates a user
based on the date of birth. Same thing as this one, this one creates based on
the Social Security number, as you can see
here, and this one creates a user based
on the date of birth. Cool. So we created a user
here with an invalid age. We created our Mc instance
in our user data mooc. Let me go ahead and update
this to insert user. And then basically, we're
asserting that this should throw an invalid
data exception. So let me run this test, and let's see what happens. Cool. As expected, the test
failed because we haven't implemented the logic
for the age validation. So let's go ahead and look
at the code base, right? So let's go ahead and look at the Social Security
number class. So this type of
implementation is something that is used in TDD, right, test driven development, where
you first write your test, they fail, and then you
write the code to fix it. It's really helpful because
the test is actually validating for what we're
looking for, right? We're looking for an exception when the age is
younger than ten. And we haven't implemented
yet, so it kind of, like, it alerts us, Hey, you have to implement
this before you can move on. So it's a pretty nice
way of developing. So let's go ahead and let's
implement the age validation. So I'm going to go ahead
and paste some code, again, just for
productivity purposes. And then we can
go over the code. So this is an age validator. It's basically a bowl
that's, you know, returning if the age is
valid, if it's true or not. Here, there's a method called get age where we get the age. So this calculates the age
based on the date of birth. And then if the age
is less than equal to 80 or greater than
or equal to ten, a return is true, right? So it has to be 10-80-years-old. And now we can use this is valid age within our
code base, right? So I can use this. I can actually have
a IF statement here. I is valid age, and we can get the
date of birth. If it's not valid or
if age is not valid, then we can throw
invalid data exception. And let's just say
invalid age. Cool. So now we have our
age validation. So let's go ahead and
run this test again. And there you go. The age
validation passed, right? So we've implemented our logic, we've implemented our unit test, and we validated the age less
than 10-years-old, right? And now I encourage you
to write a unit test to validate the age
other than 80, right? So we can meet the
business rules.
19. Unit Testing the Requirements Part 2: Hey there. Welcome to the lesson on unit testing
the requirements part two. So let's review
the requirements. We first created an application that inserts a user
to a database. We then inserted the
following data, first name, last name, date of birth, and user Social Security number. We validated the age, which is less than ten, and then we're going
to go ahead and move on to the Social
Security number. So a Social Security
number must be valid. For that, we have the
following rules, right? The source security
number has the format of a three digit number two digit
number four digit number. The first part is
called the area number. The second part is
called the group number, and the last part is
called the serial number. The area number cannot be
000-66-6999 or 900 or 999. The group number
may not be 00 and the serial number
may not be 0000. So let's go ahead and
look at some code. So we're going to
follow a TDD approach, and we're going to first write the unit test and then go ahead and
implement our validation. So for productivity purposes, I'm going to go ahead
and pay some code, and we're going to
go over this code. So I created a theory
that tests the area. I'm testing for area that
starts with 000666 900 or 999. And basically, the name
of the method or yeah, the name of the method is
the name of the method under test and then what you know,
should throw exception. So we're expecting an exception
when the area is invalid. So here we have we create a Social Security number based on the inline data parameters. So all of these are invalid. We have the Mc for the Social Security
number business class, and then we have the Mc
for the eye user data. I'm going to go ahead and
fix this insert user, and we call our action and we
are expecting an exception. So if I run this unit test, Let me expand. It does not throw an exception. It actually throws an
exception. Let's look at this. So it's actually
throwing an exception. Let's look at our create user based on the date of birth or actually the
Social Security number. So the date of birth
is date time now. That means that the user
is younger than ten. So let's go ahead and fix this. Let me say add years -11. Let's go ahead and run this
again. And there you go. Now, it's not throwing an
exception as expected because the area is not being validated within
our business logic. So let's go ahead and
look at the area here. So when we go to the is valid
Social Security number, we can see that we don't
have any implementation yet. So first thing we're going to do is implement the
logic for the area. So let me go ahead
and pay some code. So the first thing
we did here is grab the first three digits of the Social Security number. So we're getting the
first three digits, which represents the area. And if the area is 000666
900 or 999, we return false. And basically, this is
validating the area, right? So now we have the
area validation. Let's go back to our
unit test and run this. And there you go. No
exceptions were thrown. Actually, exceptions were thrown and the test passed, right? So we expect an exception
to be thrown because if the source security
number is invalid, it throws an
invalidated exception. Cool. So let's go ahead
and move on to our group. So let's create the unit
test for our group. So for that, I'm going
to go scroll down here to let me just close this. So scroll down here and
create this method. So this one is called insert user should throw an
exception when invalid group. So an invalid group cannot
contain the digit 00. In this case, I'm
going to update this, insert user, and Walla
there we have it. So let me run this, and this is not so it does not throw an exception because
we actually were not validating the group,
so it failed, right? We're expecting an exception, but it did not
throw an exception. So let's go ahead and go back to the social security
number validation, and let's implement the logic
for the group validation, which is very similar here. So we're going to get the
substring for the group. So it's between, it
starts on the index four, and it's length of two. So this kind of gets the
substring for the group, and if it's 00, then
it returns false. I can just click here
and run my test. And there you go. An exception is thrown, so the test passes. And last but not least, let's go ahead and
implement our unit test for the serial
number validation. Very similar to
the previous ones. So let me minimize this. Insert user should throw an exception when
invalid serial. Let me correct this. Insert user. So
here we're looking for serial number that
is equal to 0000. If that is the case, it
should throw an exception. So we would expect an exception. If I run this unit
test, it should fail. It fails because we haven't
implemented that yet. And if we go back to
the implementation, we can go ahead and just
implement that part. So here I'm going to
create the serial section. We're going to get the
last three digits. And if those last three digits equals to zero, zero, zero, then we'll return false, and that will throw an
exception over here, right? So if I re run this
test, there you have it. We've implemented
our testing logic based on failure scenarios. Of course, you can explore
passing and failing scenarios. I always like to start with
the failing scenarios, but then here you can
explore even more. With this type of unit testing with this type
of implementation, it's a lot safer to
make changes, right? I can easily make changes
to the business logic. And run my NICS to make
sure that, you know, all the validations based
on my requirements are met. So, you know, this gives
developers, you know, it gives software engineers
a sense of security. You know, you can
evolve your software. You can work with your
software and make sure that, you know, basic functionality, basic requirements
are being met.
20. Unit Testing the Requirements Part 3: Hey, guys. Welcome to the lesson on unit testing
the requirements P three. So we've implemented our area
group and zero validation. However, we're still missing a validation for the
Social Security number. We haven't yet unit tested the format of the
Social Security number. So let's go ahead
and implement that. Let me go ahead and copy this one because we're
going to use theory, and I'm going to paste it here. I'm going to say Insert user should throw an exception
when invalid format. Cool. And let me go ahead and test a couple
of invalid formats. So I'm going to
say AAA one, two. So this is an invalid
format. Let's see here. One, two, three,
instead of a dash, I'm going to put a number,
so not having the dashes. Let's think here. Let's have null null
as valid format. Let's also have empty. And let's just run this to kind of see what
happens, right? So we haven't implemented
any format validation. Let's see if an exception is not thrown for any one of these, then, you know, we already
have a couple of errors. So let's run this So an exception is not
thrown for this one, and for the seems
like the null, right? So two of our test cases failed. We're basically
expecting an exception for all of these
invalid formats. And there's probably a lot more invalid formats
that, you know, if we stop to think or,
you know, think about it, there's probably a lot more you can insert or
you can have here. And this is pretty nice, right? You have one method that's validating like four
invalid formats. So let's go ahead and
implement our format logic. So let's go to the is valid
Social Security number, and the first thing we're going
to do is validate format. So I'm going to go
ahead and paste this code here so
we can go over it. And here we're going to use
a library called Regex, which is a library that helps us validate formats based on
regular expression, right? So here we're checking
if there's a match where the Social Security number
has a three digit number, and then there's a dash, and then I'm expecting
a two digit number. And then another
dash and then I'm expecting a four digit number. So basically, this is validating for the
Social Security number. We need three numbers, two
numbers, four numbers. If it's a letter or something
else, if it's not a number, if there is no dash, then
the validation should fail. So let me go ahead and
run this test again. And there you go. All of these all of these all of this data here is invalid
and our unit test validated that all of these inputs are thrown in an
exception, right? So we're expecting an exception, as you can see here, and all of them are now
thrown in exception. So we successfully
validated the format.
21. Code Coverage: Hey, guys. Welcome to the
lesson on code coverage. So let's go ahead and analyze
our code coverage for Social Security requirements
implementation. So first thing we're
going to do is click on View other Windows and then search for
fine code coverage. I'm going to go ahead and
click on the Test tab, click on Test Explorer. I'm going to filter the Social Security
number test classes, and I'm going to
run all my tests for Social Security number. And now we have our fine code
coverage for those classes. Let's analyze the Social
Security number class here. Here we have 97.2% line coverage
and 88.4% code coverage. If we open the class here, we might be able to
see some results. Here we're covering
all these lines, we're covering all these lines. For the age validation, we are missing the validation of age bigger than 80 or greater
than or equal to 80, right? So remember, this is the part of the branch that is missing. And then also the get age. Even though it's a private
method, we're still missing, you know, the get age, which, to be honest, might not be that important. However, remember, code coverage helps us analyze
what we've missed, right? So let's say that you're implementing
your business logic. You understand that
you missed this, but you consider this not to
be that important, right? Maybe what's more important is your actual business logic, which is your serial number, your group, your
area validation, of course, your age
validation is important. We want to make sure
we cover all of that. So this could be some
homework if you want to uh, you know, explore and implement
and get 100% unit test. I think this would be a
great opportunity for that and also validate
the age that is greater than 80 is another
something that you could do and hopefully get 100% if that's something
that you would desire. So the real value
here is, you know, like I've previously mentioned, is validating your
business rules, right? We've implemented
an application, a very simple application that has no access
to the database, but we did simulate database
operations and inserts. We have our business rules implemented for the
Social Security number, and we were able to validate, most of our business rules. We validated most of
our business rule. This gives, you know, software engineers
more security. It gives us, you know, power to make changes or
security to make changes, right? We can make changes
and in a sense, guarantee that our
business rules, our logic is being met
when you refactor, when you update your code
base, and you make changes. This is a very
simple application, however, unit tests
are small, right? So when you get to
the real world, you're still writing
small methods. You're still writing, you know, you're still
validating your logic. Even though this is
simple, this is very applicable to real
world scenarios. I hope that this analyzing
the code coverage, implementing unit test,
reviewing the requirements, open your eyes to the
value of unit test. The goal here is to, um, sure that you as a
software engineer, understand the value
of unit tests, how it can help you
evolve as a developer, help you evolve your
code base, right? Write better code, write smaller concise methods and
validate those methods, validate your rules
where you can have a software
that could evolve, right to better
architecture or to meet whatever business
requirements or the goal of your product is.
22. Course Conclusion: Hey, there. Welcome to
the course conclusion. First of all, if you're
watching this video, I would like to congratulate
you for reaching this far. I would say that 90% of
professionals don't invest the time they could
or sure in learning, and you are part of the 10%. Index in this section, we will go over the main topics taught in throughout the course. So let's overview. First, we went over the software
testing fundamentals. We talked about why software
testing is important, such as the cost of
bugs, some excuses, testing excuses, and also
principles of software testing. We went over the definition of unit testing where
we talked about pros and cons and how do
we effectively unit test. We also talked about
integration testing, where we looked at a
testing balance to compare unit testing to
integration testing. And then we went
over to ten tests, where we also looked at
the pros and cons and when ten tests are
most commonly used. Finally, we looked at a
testing pyramid where we compare unit integration
and into end testing, and we had an image where we checked reality and complexity based on the type of testing. Next, we looked at a use case. We checked out the
Caesar scholarship. We analyzed the input and output analysis with its
basic requirements. We then released, right? We did a production
release thoughts of how this application
would be released. And then we went over the
first steps on unit testing, like testing the
award calculations. Finally, we talked
about the unit testing with X it library. We discussed why
this library is so important and why
we chose to use it. We then talked about
the test project layer within Visual Studio. We worked with the
test explorer, where we ran unit test based on present condition or
preset conditions. We then worked on formatting or tests where we analyze
the fact attributes, the theory attributes,
the AA pattern, and some testing
naming conventions. We then moved forward with assertions and
fluent assertions, where we tested types
such as Booleans, exceptions, numerics,
and other types. We looked at fluent assertions
for better readability. We then went over code coverage and its basic definitions. We then talked about requirements
for software testing. The requirements were based on birth date and
Social Security number. We created a user entity, a user data, Social Security
number class implementation. Then we understood that
we had to mock data, so we used the MQ
library for that. We also use libraries such as Automc to facilitate mocking, and then we implemented
mocking with our data access. We then implemented
the unit tests for the age in the Social
Security classes. And finally, we
did an analysis of the code coverage based
on the code that we've written. So that was a lot. I would like to thank you
for watching this course. My hope that this
help you understand the importance of testing
and applications, and this will help you evolve
as a software engineer. Remember, to keep learning
and evolving until next time.