# 01.6-SpringBoot basics-Lesson6 Testing ###### tags: `Udacity` [ToC] # 01 Testing with jUnit and Selenium {%youtube gvKprZ0TCck%} ![](https://i.imgur.com/GPKyrCw.png) Lesson Outline * **Testing with JUnit**: We introduce JUnit, the standard testing and assertion framework for Java. We learn how to write new tests and check out assumptions by using JUnit's extensive library of assertion methods. * **JUnit in Situ**: We look at how we can use IntelliJ (or any other IDE) to run tests, debug errors, and get reports about code coverage. * **Selenium/WebDriver**: We introduce Selenium, a tool for browser automation, through its Java API, `selenium-java`. We look at how to write Selenium scripts to simulate user actions in a browser at a high level. * **JUnit and Selenium**: We discuss how to use Selenium's java API inside of JUnit tests, which opens the door to an advanced technique: automated user testing. # 02 Big Picture Testing: Why and How {%youtube V4R_I9IpUEA%} **The Test-Driven Development Lifecycle** ![](https://i.imgur.com/M11q4GR.png) Testing is an important and highly desired part of the software development process. In a world where uptime and user retention means everything, it's important to validate that your application actually does what it's supposed to before it goes into production. The standard accepted way to enforce this is by adopting a test-driven development lifecycle, or TDD. In this model, the "red then green" philosophy is dominant - tests should be written before the feature to be tested, meaning that they start off failing - aka, the tests are "red." Then, as the feature is implemented, one test after another should start to pass - aka, become "green." To facilitate this approach, it's useful to have a standard way to describe features or requirements to be tested. For this, we turn to the concept of a "user story." A user story describes the functionality a feature should have from the perspective of a user interacting with the application. Typically, the format of a user story is: **As a user, I can *take some action* in order to *achieve some goal*.** Often a feature will be broken up into many user stories, each of which should correspond to at least one test to be implemented for that feature. If all the tests pass, it means that all of the user stories are successfully implemented, and the feature is complete. Key Terms * Test Driven Development: a software development methodology that emphasizes writing tests before the code to be tested. This gives developers a roadmap to success - once all the tests are passing, the feature is complete! * User Story: User stories are short sentences derived from feature requirements in the format of As a user, I can in order to . These are used to create tests to verify the components of a feature. # 03 Intuition Developing Your Intuition About Testing {%youtube K9Y83WIJ40A%} There are many different types of tests meant to validate different types of features and different layers of an application. In this course, we're going to focus on two specific types of tests: Unit tests and integration tests. Unit tests are meant to test a single unit or component of an application or process - these tests should be simple, and verify that a specific method, component, or process step acts as expected according to its inputs. Sometimes you'll also use unit tests to verify that the unit under test fails predictably, as well; it's good to test both positive and negative conditions in a unit test! Integration tests are the next layer up from unit tests. Instead of testing a single unit of an application, they test multiple units and how they integrate with one another. Often, an integration test will validate an entire user story, for example, while a unit test will validate a single step in the process a user story describes. The rule of thumb is that unit tests should be used to test invariants - conditions that do not change - and integration tests should be used to test user actions and entire process flows . Key Terms * Unit Tests: A unit test only validates the smallest unit of a computational process. That might mean a test of a single method, or a single component in an application. * Invariants: An invariant is a law of computation, something that shouldn't change despite changing circumstances. For example, adding 0 to a number should always result in the original number, and dividing by 0 should always result in an error. * Integration Tests: Integration tests are intended to validate the operation of multiple application components as they interact with each other - or integrate with one another. ==Shannon Note== * Unit Test * 最簡單 最低階的test 為了測試single unit or a component of an application * ex. 檢查單獨方法的某個特定input * ex. 檢查一個物件確實被Update * 獨立測試每個步驟 * 通常會使用去測試不會改變的情況(invariant) * 透過inputs including edge cases去測試application behavior * Integration Test * 測試一群元件彼此的互動 * ex. 像是application與database之間的存取 * ex. 如何回應http request * 檢查整個系統運作的 * 通常會去測試使用者行為和處理的流程 * 模仿使用者行為和觸發複雜的處理在application裡面 # 04 Testing with JUnit Testing with JUnit {%youtube Ckz3UlX6Y0k%} JUnit is the standard Java testing framework, and despite its name, it is capable of much more than unit tests. JUnit expects all tests for an application to be collected in class files, just like any other Java code. JUnit provides an annotation, `@Test`, that can be placed on a method in a test class to declare a single test. Each method annotated like this can be either executed individually, or in a group - and in both cases, JUnit will generate a report that lists each test that was run, and whether it was successful or not. In order for JUnit to know if a test is successful or not we need to use assertions. `@Test`-annotated methods should not have a return value! Instead, we can use special methods provided by JUnit to check our assumptions about the code under test. We'll look at a concrete example of this in the next video. Sometimes, we need to initialize some data or objects to be used in our test methods. JUnit provides a few extra annotations to define this initialization code. `@BeforeEach`- and `@AfterEach`-annotated methods will be called before an after each `@Test`-annotated method, respectively, and `@BeforeAll`- and `@AfterAll`-annotated methods will be called at the before and after all tests have been executed, respectively. ==Shannon Note== >* JUnit是一個測試型的library for java >* JUnit會測試完所有的方法,儘管途中有發生錯誤,但這也代表每個方法並沒有互相依賴,但如果有互相共用的方法也可以透過新增一個non-test method to the class as a helper function >* 如果data需要被重新實現或是需要被清理after testing, 可以透過增加annotated method去告訴JUnit如何處理 >* assertion: junit提供一個特定的方法可以確認某些特定的條件 ```java public void testGetMessage(){ assertEquals("希望得到的答案", 要測試的方法) } ``` >JUnit分成兩大類 >* 1. individual test * `@BeforeEach`, `@Test`, `@AfterEach` * `@BeforeEach` 單個測試之前會被執行 * `@AfterEach` 單個測試之後會被執行 * `@Test` 代表這是一個single test, 所有被標上這個標記的方法可以被單獨執行誘惑是在一個group裡面被執行 > >* 2. executed at the beginning and end of all testing in the class * `@BeforeAll`, `@AfterAll` * `@BeforeAll` 所有測試完之前會被執行 * `@AfterAll` 所有測試完之後會被執行 > * @BeforeAll and @AfterAll 通常會被用來使用在管理靜態資源static resources which are shared by all tests like lookup table, drivers and web connections > * @BeforeEach and @AfterEach 通常會被用在管理任何事情 with state to be updated by a test. {%youtube OAF99el_0oY%} [For the full lecture sample code from the previous video, click here](https://github.com/udacity/nd035-c1-spring-boot-basics-examples/tree/master/udacity-jwdnd-c1-l4-testing-with-junit-master) In the previous video example, we looked at some basic JUnit tests to learn more about JUnit's annotations and assertions. Some key takeaways: * In a Maven project, it's extremely important to make sure your JUnit test classes are in the right directory. Maven expects tests to be in the `src/test/java` directory. Always double check! * JUnit's assertions are all static methods on the `org.junit.jupiter.api.Assertions` class, so to use them you need to statically import the methods you need * The most commonly-used assertion is `assertEquals`, which can be used to check if the result of some action is equal to the expected result. * Another common assertion is `assertThrows`, which is used to check if a given piece of code does throw an exception as expected. This can be useful to check so-called negative test cases, where we want to make sure our application fails in the correct way. This assertion uses Java 8's lambda expression syntax to capture a piece of code to test - if you're not familiar with this syntax, you can find more information about it in the further research section below. * ==@BeforeEach== -annotated methods are particularly useful for initializing some data that needs to be in the same state for every test. For example in the video, we used this to ensure that a list under test always has the same values at the beginning of each test. **The Lifecycle of a JUnit Test Class** ![](https://i.imgur.com/35JcX99.png) Key Terms * **Assertion**: an assertion, in the context of JUnit, is a method we can call to check our assumptions about the behavior of the unit under test. If our assumptions are correct, the assertion silently returns and the test method continues. If they're false, the assertion throws a special exception class that JUnit uses to build the final failure report, and the test method halts execution. **Further Research** * [Official JUnit User Guide](https://junit.org/junit5/docs/current/user-guide/) * [Official Java Tutorial on Java 8's Lambda Expression Syntax](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html) ==Shannon Note== >* 在POM.xml裡面 `<scope>test</scope>`表示JUnit classes and annotations只能在 `source/text` java folder中運行 ```typescript <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.6.2</version> <scope>test</scope> </dependency> </dependencies> ``` > Testing Code ```typescript import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest class L5TestingWithJUnitApplicationTests { private List<Integer> testList; //每次執行Test之前 就先呼叫 @BeforeEach public void beforeEach(){ //他把testList實體化 並填入數字 testList = new ArrayList<>(); testList.add(1); testList.add(2); testList.add(3); } // after each test, it's nullified by the @AfterEach method @AfterEach public void afterEach(){ // testList = null; } //test測試 @Test public void testAddZero(){ int a = 10; int b = 0; int c = a + b; //assertEquals 是一個靜態方法 (expect value, actual value) assertEquals(a,c); } @Test public void testDivideZero(){ int a = 10; int b = 0; //我們期望當10/0時會throw ArithmeticException Exception /* 如果我們要測試丟出來的Exception是我們所預期的 可以使用assertThrows Which executes a given chunk of code and passes if the specified exception type was thrown */ assertThrows(ArithmeticException.class, () ->{ int c = a / b; }); } @Test public void testListContains(){ /*this method checks if the list contains the num 1 這個方法一定是對的 因為@BeforeEach會建立好testList物件且放數字進去 我們一定要使用@BeforeEach或是@AfterEach而不是all 因為這樣Each test gets a fresh list with the same data to play around with adding or removing that the test requires */ assertTrue(testList.contains(1)); //this method removes the first element of the list testList.remove(0); //this method checks the list did not contains the num 1 assertFalse(testList.contains(1)); } } ``` # 05 Quiz: JUnit Quizzes Which of the following entries describe the order of operations for Test-Driven Development? [O] Requirements are created first, and then unit tests are written that verify the requirements. Code is written to make the unit tests pass. # 06 Exercise: JUnit Exercise: Testing with JUnit Let’s look at what it could be like developing to pass a unit test. Here’s a unit test for a partial implementation of the iconic “FizzBuzz” problem, a simple test suggested in 2007 as a way to demonstrate minimal programming skills. Our unit test checks a variety of values and confirms the expected responses. Your job is to fill in the `FizzBuzzService.fizzBuzz(int number)` method so that it passes our unit test. A more complete description of the requirements is found in the Service, but can also be inferred from the unit tests. > Testing with JUnit Task List This project will be built locally on your machine using the following instructions: 1. Download the starter code [here](https://video.udacity-data.com/topher/2020/June/5eded445_l5e1/l5e1.zip). It can also be found in the Resources tab titled `l5e1.zip` 2. Open the project in Intellij 3. Open the `Course1ApplicationTests.java` and `FizzBuzzService.java` files. 4. Complete the FizzBuzz algorithm such that is passes all tests in `Course1ApplicationTests.java`! # 07 Solution: JUnit Solution: Testing with JUnit {%youtube _BqTDhjEqL0%} There are lots of different ways to solve this problem, but let's look at a simple one. ```typescript public String fizzBuzz(int number) { } if (number % 3 == 0) { return "Fizz"; } else if (number % 5 == 0) { return "Buzz"; } else { return "" + number; } } ``` This solution passes our first three blocks of tests, but fails on the check for divisible by 3 and 5. That part can be passed by checking for divisible by both 3 and 5 (or checking for divisible by 15) first: ```typescript public String fizzBuzz(int number) { if (number % 3 == 0 && number % 5 == 0) { return "FizzBuzz"; } else if (number % 3 == 0) { return "Fizz"; } else if (number % 5 == 0) { return "Buzz"; } else { return "" + number; } } ``` It still fails the final part of the unit test, however. `assertThrows` expects that an `IllegalArgumentException` is thrown in the event that we try to pass in a 0 or -1. Let's try adding the negative number check before returning: ```typescript public String fizzBuzz(int number) { if (number % 3 == 0 && number % 5 == 0) { return "FizzBuzz"; } else if (number % 3 == 0) { return "Fizz"; } else if (number % 5 == 0) { return "Buzz"; } else if (number < 1) { throw new IllegalArgumentException("Value must be greater than 0"); } else { return "" + number; } } ``` This almost works, but it turns out that 0 mod 3 = 0, and so passing 0 in returns FizzBuzz instead of throwing our exception. The check must be at the top to the top to pass all our unit tests: ```typescript public String fizzBuzz(int number) { if (number < 1) { throw new IllegalArgumentException("Value must be greater than 0"); } else if (number % 3 == 0 && number % 5 == 0) { return "FizzBuzz"; } else if (number % 3 == 0) { return "Fizz"; } else if (number % 5 == 0) { return "Buzz"; } else { return "" + number; } } ``` These unit tests are pretty thorough, but they are not perfect. Consider the following example: ```typescript public String fizzBuzz(int number) { if (number == 0 || number == -1) { throw new IllegalArgumentException("Value must be greater than 0"); } else if (number == 15 || number == 75) { return "FizzBuzz"; } else if (number % 3 == 0) { return "Fizz"; } else if (number % 5 == 0) { return "Buzz"; } else { return "" + number; } } ``` t's not always practical to test every possible input and output, and so the main goal of our unit tests is to test a good selection of reasonable values, and some typical boundary cases. We could run a loop in this test and look for hundreds of values, but at a certain point you're just reimplementing the program inside the unit test and it's not worth it. Go for the biggest bang for your buck and rely on integration testing to deal with the occasional outliers! # 08 JUnit in Situ {%youtube Y6hk-YaZS1U%} When we write tests, it's with the intention to run them and report on the results. Test runners like JUnit provide many ways to report the results of a test run, but one of the most useful ways to interact with that reporting is through an IDE, like IntelliJ. There are three main advantages to running JUnit tests from an IDE: * **Interactive Reporting**: When we run tests in an IDE, we can usually inspect the results of each test individually. If an assertion fails or an unexpected exception is triggered, the stack trace and circumstances will be shown in the details for each test, and clickable links in the results help you navigate to problem areas in your code. * **Interactive Debugging**: When a pernicious problem persists, it can often be helpful to step through the code's execution line-by-line to inspect both the control flow and the values in memory used by the program. This is called debugging, and while it's technically possible to do outside of an IDE, IDEs like IntelliJ provide many useful tools for making the process as painless as possible. * **Code Coverage Reports**: When we run code in an IDE like IntelliJ, we can choose to have the IDE track which lines of our code were visited, and how many times. This can be wildly useful when trying to track down why a branch of a condition isn't being reached, as well as when determining how much the entire code base is covered by the currently-implemented tests. In the next video, we'll take a look at some of these features in IntelliJ while exploring a real-world scenario - fixing failing tests. {%youtube AXx8eaP8Gfg%} [For the full lecture sample code from the previous and next videos, click here](https://github.com/udacity/nd035-c1-spring-boot-basics-examples/tree/master/udacity-jwdnd-c1-l5-junit-in-situ-master) In this first foray into fixing failing tests, we ran our tests within IntelliJ to get a report of the status of all tests. Initially, these were all failing, but by clicking through IntellliJ's test report details, we quickly discovered a common problem to all of the tests: some of the data under test wasn't being initialized at all! We solved this by adding an @BeforeAll-annotated method responsible for that initialization logic. Running the tests again, our report shows that some are slowly turning green - progress! All we needed was a handy overview of the test results, and we could quickly identify a common problem between them. In the next video, we'll explore another tool the IDE provides for this kind of work - debugging. {%youtube rmAH2L4O8A4%} In this second attempt to fix our failing tests, we used IntelliJ's debugger to check our code under test line-by-line. We found that the conditions our test was validating did not match the code under test - which means we had a decision to make. Usually, in cases like this, where the test does not match the code it is testing, we have to decide which is correct. In a real-world development scenario, we would check both against the technical requirements provided to us, but since this is just an example, we chose to assume that the code under test was correct. In any case, debugging helped us find an issue that otherwise might be hard to find. In the next video, we'll try to get the remaining tests passing, and we'll see how code coverage can help us determine if our code is being sufficiently tested. {%youtube 2J2mqZQ4deI%} [For the full lecture sample code from the previous videos, click here](https://github.com/udacity/nd035-c1-spring-boot-basics-examples/tree/master/udacity-jwdnd-c1-l5-junit-in-situ-master) In this final push to get our tests passing, we looked at the remaining failing tests. The first was another issue of test/code not being in sync: our test expected a specific exception to be thrown, but the code under test wasn't throwing that exception! This is a good example of why `assertThrows` is useful in a testing context; if our feature requirements or documentation say that a method should throw an exception under certain circumstances, it can cause real problems if it does anything else. Moving on to the remaining failing test, we saw that it was performing the exact same test as a previous successful test. This is usually a good sign that these tests rely on some data that needs to be initialized identically before each test. Indeed, we found that the data we initialized with an `@BeforeAll`-annotated method actually needed to be initialized before each test, not all of them, so we changed the `@BeforeAll `annotation to `@BeforeEach`. Finally, to verify that our tests weren't overlooking anything in our code base, we re-ran them with IntelliJ's code coverage feature. This showed us that while our tests were covering nearly all of the lines of code in the project, there was one method we weren't testing at all. After adding a test for that method and re-running the test suite with coverage, we saw our entire codebase lit up in green. Nice! **Red, Then Green: The Test-Driven-Development Motto** ![](https://i.imgur.com/LnbNQj6.png) **Key Terms** * **Interactive Reporting**: When we run tests in an IDE, we can usually inspect the results of each test individually. If an assertion fails or an unexpected exception is triggered, the stack trace and circumstances will be shown in the details for each test, and clickable links in the results help you navigate to problem areas in your code. * **Interactive Debugging**: When a pernicious problem persists, it can often be helpful to step through the code's execution line-by-line to inspect both the control flow and the values in memory used by the program. This is called debugging, and while it's technically possible to do outside of an IDE, IDEs like IntelliJ provide many useful tools for making the process as painless as possible. * **Code Coverage Reports**: When we run code in an IDE like IntelliJ, we can choose to have the IDE track which lines of our code were visited, and how many times. This can be wildly useful when trying to track down why a branch of a condition isn't being reached, as well as when determining how much the entire code base is covered by the currently-implemented tests. **Further Research** * [An Overview of IntelliJ's Test Running Features](https://www.jetbrains.com/help/idea/performing-tests.html) * [An Overview of All of the Annotations Available in JUnit](https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations) * [An Overview of Most of the Assertions Available in JUnit](https://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions) # 09 Quiz: JUnit in Situ Quizzes Now that our taco delivery service has gone viral, we need to start making sure our code is appropriately tested. Let’s look at an imperfect example of DeliveryService class and a corresponding test: > DeliveryService.java ```typescript @Service public class DeliveryService { DeliveryMapper deliveryMapper; OrderService orderService; public DeliveryService(DeliveryMapper deliveryMapper, OrderService orderService) { this.deliveryMapper = deliveryMapper; this.orderService = orderService; } public Delivery scheduleDelivery(LocalDateTime time, Integer orderId) { if(time.isBefore(LocalDateTime.now())) { throw new IllegalArgumentException("Cannot schedule a delivery in the past"); } if(orderService.getTacos(orderId).size() <= 0) { throw new IllegalArgumentException("Cannot schedule a delivery for 0 tacos. Why would you do that??"); } System.out.println("Scheduling a Delivery"); Delivery delivery = new Delivery(); delivery.setTime(Timestamp.valueOf(time)); delivery.setOrderId(orderId); Integer id = deliveryMapper.insert(delivery); delivery.setOrderId(id); return delivery; } public Delivery findDelivery(Integer deliveryId) { return deliveryMapper.findDelivery(deliveryId); } public List<TacoOrder> findTacos(Integer deliveryId) { return deliveryMapper.findTacosForDelivery(deliveryId); } } ``` >DeliveryServiceTest.java ```typescript @Test void testScheduleDelivery() { TacoOrder tacos = new TacoOrder(); tacos.setTacoName("Fish Fiesta"); tacos.setCount(3); tacos.setTacoPrice(3.33); orderService.addItemToOrder(tacos, ORDER_ID); deliveryService.scheduleDelivery(LocalDateTime.now().plusHours(1), ORDER_ID); Delivery delivery = deliveryService.findDelivery(ORDER_ID); Assertions.assertEquals(ORDER_ID, delivery.getOrderId()); Assertions.assertArrayEquals(new TacoOrder[]{tacos}, deliveryService.findTacos(delivery.getOrderId()).toArray()); } ``` Your job is to figure out where our unit test is inadequate. Which of the following lines from DeliveryService will be tested by our unit test? (X) `throw new IllegalArgumentException("Cannot schedule a delivery in the past");` (X) `throw new IllegalArgumentException("Cannot schedule a delivery for 0 tacos. Why would you do that?");` (0) `System.out.println("Scheduling a Delivery");` (0) `return deliveryMapper.findDelivery(deliveryId);` (0) `return deliveryMapper.findTacosForDelivery(delilveryId);` # 10 Exercise: JUnit in Situ Exercise: JUnit in Situ Remember the FizzBuzz problem from our previous exercise? Now we’re taking it to the next level! Here’s a naive implementation of a buzzFizz method. It’s like `FizzBuzz`, but in reverse! ```typescript /** * Reverse fizzBuzz. Takes the output value and returns * the number that would produce it. The occurrence param * indicates how many times the input string should have happened, if counting up from 1. * * For example, if the input is "Buzz" and occurrence is 2, the output should be 10, * because "Buzz" is produced for the values 5 and 10 in FizzBuzz. */ public int buzzFizz(String input, int occurrence) { if (input.equals("Fizz")) { return 3 * occurrence; } else if (input.equals("Buzz")) { return 5 * occurrence; } else if (input.equals("FizzBuzz")) { return 15 * occurrence; } else { return Integer.valueOf(input); } } ``` It’s got some problems, though, and it’s your job to write a unit test that proves it! For this assignment, you should write one or more unit tests for `buzzFizz`. You should definitely test all the things it does right, but you should also test for things it will get wrong. Does it seem like the requirements of `buzzFizz` are unclear? Write unit tests for those scenarios too! Write a test for everything you think should happen if the program covered all scenarios. JUnit in Situ Task List This project will be built locally on your machine using the following instructions: 1. Download the starter code [here](https://video.udacity-data.com/topher/2020/June/5eded990_l5e2/l5e2.zip). 2. Open the project in Intellij 3. Open the files `FizzBuzzService.java` and `FizzBuzzServiceTest.java` 4. Look at the buzzFizz method in `FizzBuzzService.java` and write unit tests for it in `FizzBuzzServiceTest.java`! # 11 Solution: JUnit in Situ Solution: JUnit in Situ {%youtube N3h_89BkAuo%} There are quite a few holes in this implementation. Here’s one way to organize your tests to identify failures: > FizzBuzzServiceTest.java ```typescript @Test void testBuzzFizz_happyPath() { FizzBuzzService fbs = new FizzBuzzService(); // expected to pass assertEquals(1, fbs.buzzFizz("1", 1)); assertEquals(101, fbs.buzzFizz("101", 1)); assertEquals(3, fbs.buzzFizz("Fizz", 1)); assertEquals(9, fbs.buzzFizz("Fizz", 3)); assertEquals(5, fbs.buzzFizz("Buzz", 1)); assertEquals(10, fbs.buzzFizz("Buzz", 2)); assertEquals(15, fbs.buzzFizz("FizzBuzz", 1)); assertEquals(30, fbs.buzzFizz("FizzBuzz", 2)); } @Test void testBuzzFizz_unclearRepetition() { FizzBuzzService fbs = new FizzBuzzService(); // requirements unclear - does "FizzBuzz" count as a "Fizz" and a "Buzz" as well? // both these tests fail because they return '15', which is "FizzBuzz" assertEquals(18, fbs.buzzFizz("Fizz", 5)); assertEquals(20, fbs.buzzFizz("Buzz", 3)); } @Test void testBuzzFizz_invalidStrings() { FizzBuzzService fbs = new FizzBuzzService(); // should this be case insensitive? assertEquals(3, fbs.buzzFizz("fizz", 1)); //throws number format exception // what to do about nonsense input? assertThrows(IllegalArgumentException.class, () -> fbs.buzzFizz("tacocat", 1)); } @Test void testBuzzFizz_boundaryChecking() { FizzBuzzService fbs = new FizzBuzzService(); // how should the program represent that no input produces the output. This example would // return the integer -1, which is incorrect. Should we throw an exception, return 0 or some other value? assertThrows(IllegalArgumentException.class, () -> fbs.buzzFizz("-1", 1)); // what about integers recurrence? There should never be a second occurrence // of "1", so what do we expect the program to do? assertThrows(IllegalArgumentException.class, () -> fbs.buzzFizz("1", 2)); // we can also enter invalid occurrence param for "Fizz" or "Buzz", getting back 0 or negative numbers assertThrows(IllegalArgumentException.class, () -> fbs.buzzFizz("Fizz", 0)); // returns 0 assertThrows(IllegalArgumentException.class, () -> fbs.buzzFizz("Buzz", -1)); // returns -5 } ``` # 12 Selenium/WebDriver {%youtube zniCcO2lgX0%} Our goal in this section is to expand our testing acumen beyond simple unit tests into the realm of integration tests. Specifically, we want to be able to test our web application's abilities from the high-level perspective of user actions. In order to do this, we need a way to programmatically simulate a user's action in the browser. That's where Selenium comes in. Selenium is a cross-platform tool for browser automation and scripting, and we're going to use it to write tests that simulate a user's actions in a browser. In the next video, we'll look at how Selenium's API functions in detail. {%youtube zynznmvRcL0%} **The Architecture Selenium** ![](https://i.imgur.com/aWg8lJt.png) [For the complete lecture code sample from the previous video, click here](https://github.com/udacity/nd035-c1-spring-boot-basics-examples/tree/master/udacity-jwdnd-c1-l5-selenium-webdriver-master) In this video, we looked at the basic elements of a Selenium script. Here's the full script we examined: ```typescript public static void main(String[] args) throws InterruptedException { //我們告訴WebDriverManager去確認我們是否有dromedirver set up //他會順便檢查chrome在電腦上運行的版本,使用這個去立即下載合適的webdriver WebDriverManager.chromedriver().setup(); //這一行是來自Selenium,目的在新增一個控制for the webDriver //沒有上面那一行,這行就會失效,只要有WebDriver我們就可以使用這個去網站上任何地方 WebDriver driver = new ChromeDriver(); //drver.get()會告訴dirver去探訪裡面的參數URL //如果要去探訪自己的website就要寫 例如:localhost:8080/home driver.get("http://www.google.com"); //目的:取得searchbar的內容 可以取得name為q的原件 WebElement inputFiled = driver.findElement(By.name("q")); //dirver會把這個string內容送去給element //sendKeys可以用來click links, buttons and more inputFiled.sendKeys("selenium"); //當搜尋selenium這個詞彙在google上面後 會發送submit去查詢結果 inputFiled.submit(); //其中一個最簡單的方式去選取一群的elements就是使用By.cssSelector //查詢結果包含在div with the class name "g" //each of the results has at least one 'a' tag inside of it List<WebElement> results = driver.findElements(By.cssSelector("div.g a")); //iterate over them //你會發現結果有一些null 是因為div.g a並不是那麼的精確 通常會使用id or class name // 但google沒有id class name為了要仿只this kind of external automation int count = 1; for(WebElement element : results){ String link = element.getAttribute("href"); System.out.println("這是搜尋到的第" + count++ + "筆資料:" + link); } //先讓thread暫停一下 以至於讓我們看到what our script is doing in the browser before it quits //他會顯示畫面五秒鐘後再關閉 Thread.sleep(5000); driver.quit(); // SpringApplication.run(L5SeleniumWebDriverApplication.class, args); } ``` Every Selenium script has to start by initializing a web driver. Since we're using WebDriverManager (documentation links below), we can use it to automatically download the binary file for Selenium's driver for Google Chrome, and then we can initialize the driver without any additional work. Once we have a driver, we need to tell it which web page to visit. We do this with `driver.get("http://www.google.com");` in the script, but if we were testing one of our own applications, like the message page from earlier this course, we would have to change the URL to something like `http://localhost:8080/home`. In order to interact with or extract data from the web page, we first need to select the required HTML elements on the page. In this example, we use `driver.findElement(By.name("q"));` to select the google search input element. A detailed explanation of this process can be found below. In order to interact with the elements we've selected, we can call various methods on them. In this case, we're using `inputField.sendKeys("selenium");` to simulate typing the word `selenium` into google, and we're using `inputField.submit();` to simulate submitting the search form. Once we've interacted with the web page, we want to read in the results and print them out. Again, we use the same process for finding an element, but this time, we use `driver.findElements()` to get a list of matching elements, instead of a single one. The final part of every Selenium script is shutting down the driver. Since the driver is an external program, if we don't call `driver.quit()`, the automated browser window will never close on its own. **Key Terms** * **Web Driver**: In order for Selenium to assume control of a browser, it needs a program to interface with the specific browser's API. This program is called a web driver, and there are different web drivers for each major browser. **Further Research** * [WebDriverManager on Github, with Documentation on Its Use and Motivation](https://github.com/bonigarcia/webdrivermanager) * [Official Selenium Documentation on Finding Elements on a Web Page, with Code Examples](https://www.selenium.dev/documentation/en/getting_started_with_webdriver/locating_elements/) ==ShannonNote== ```typescript // selenium-java很重要 因為她告訴intellij哪裡可以取得Selenium Java Base API <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>3.141.59</version> </dependency> //Selenium使用不同的WebDrive for each browser it tests //但如果使用webdrivermanager: 這是一個簡單的library會根據需求(你機器上擁有的browser)立即下載webdriver <dependency> <groupId>io.github.bonigarcia</groupId> <artifactId>webdrivermanager</artifactId> <version>3.8.1</version> </dependency> ``` > 搜尋頁面 * `By.name("q")` ![](https://i.imgur.com/OgZ6z4U.png) > 結果頁面 * `div.g` ![](https://i.imgur.com/0bIKbQC.png) * `div.g a` ![](https://i.imgur.com/jZahFjZ.png) # 13 Quiz: Selenium/WebDriver Quizzes Here’s a little HTML snippet from our exercise back in the lesson on Thymeleaf. Look familiar? What do you think this Selenium script will do if run on this page? home.html ```typescript </head> <body> <form action="#" th:action="@{'/simplehome'}" method="POST"> <input id=”submit” type="submit" value="Visit me"> </form> <h1 th:if="${firstVisit}">Hello, homepage!</h1> <h1 th:unless="${firstVisit}">Welcome back!</h1> </body> </html> ``` SeleniumExample.java ```typescript public static void main(String[] args) throws InterruptedException { WebDriverManager.chromedriver().setup(); WebDriver driver = new ChromeDriver(); driver.get("http://localhost:8080/simplehome"); driver.findElement(By.id(“submit”)).click(); Thread.sleep(5000); driver.quit(); } ``` > Ans: Open `http://localhost:8080/simplehome` in a new Chrome browser, find the button with the id "submit" and click it. Our previous example had an id, which is pretty handy! In fact, as a developer, you should get in the habit of always adding ids to any page elements that might be useful to reference in testing. However, we don’t always have the luxury of referencing an element by its id. Here’s a similar input field, but with no id! ```typescript <form class=”simpleForm” action="#" th:action="@{'/simplehome'}" method="POST"> <input class=”submitButton” name=”submit” type="submit" value="Visit me"> </form> ``` 可以透過以下幾種方式找到 * `driver.findElement(By.name("submit")).click();` * `driver.findElement(By.className("submitButton")).click` * `driver.findElement(By.tagName("input")).click();` * `driver.findElement(By.cssSelector("input.submitButton")).click();` * `driver.findElement(By.xpath("//input[@value='Visit me']")).click()` 補充資料: XPath :https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/315815/ # 14 Exercise: Selenium/WebDriver Exercise: Selenium/WebDriver Let’s continue our trip down memory lane and return to the web page we created a while back. Look back at your solution for Exercise: Connecting Controllers to Templates in the lesson Spring MVC and Thymeleaf. We had created a page that allows users to input an animal and an adjective and then press a button to receive a response from the server. After 5 times, however, the message changes. Pretty tricky! Sounds like something that needs a Selenium test. Your job for this assignment is to write a Selenium script that opens the page at http://localhost:8080/animal and then types in a value for each field and presses the submit button. It should submit the form 5 times and then write the conclusion message to the console. In a real test, we might attempt to do some validation, but for now we’re just exploring Selenium and learning what it can do. We’ve added some ids and classes to the html page, so all you need to do for this assignment is to fill in the main method of our SeleniumTest class. # 15 Solution: Selenium/WebDriver {%youtube I8iazfgyGSk%} Here’s a sample solution. Annotation can be found in the comments. ```typescript public static void main(String[] args) throws InterruptedException { //start the driver, open chrome to our target url WebDriverManager.chromedriver().setup(); WebDriver driver = new ChromeDriver(); driver.get("http://localhost:8080/animal"); //find the fields we want by id and fill them in WebElement inputField = driver.findElement(By.id("animalText")); inputField.sendKeys("Manatee"); inputField = driver.findElement(By.id("adjective")); inputField.sendKeys("Whirling"); //the fields don’t clear on submit for our simple app, so just submit it 5 times for(int i = 0; i < 5; i++) { inputField.submit(); List<WebElement> trainingResults = driver.findElements(By.className("trainingMessage")); System.out.println("trainingResults.size() = " + trainingResults.size()); } // then get the element by the class conclusionMessage and print it WebElement conclusionResult = driver.findElement(By.className("conclusionMessage")); System.out.println("conclusionResult.getText() = " + conclusionResult.getText()); Thread.sleep(5000); driver.quit(); } ``` ==Shannon Note== > 以上的解答行不通,因為每次Submit資料就不見了,所以submit不出去,應該要重新填寫,才能繼續submit > 以下是我的寫法 ```typescript SpringApplication.run(DemoApplication.class, args); // // //start the driver, open chrome to our target url WebDriverManager.chromedriver().setup(); WebDriver driver = new ChromeDriver(); driver.get("http://localhost:8080/animal"); //find the fields we want by id and fill them in WebElement inputField; List<WebElement> trainingMessages = new ArrayList<>(); for(int i = 0 ; i < 4; i++){ inputField = driver.findElement(By.id("animalText")); inputField.clear(); inputField.sendKeys("Cate"+i); inputField = driver.findElement(By.id("adjective")); inputField.clear(); inputField.sendKeys("Cute"+i); inputField.submit(); trainingMessages = driver.findElements(By.className("trainingMessage")); System.out.println("trainingMessages.size() = " + trainingMessages.size()); } if(trainingMessages.size() > 0){ for(WebElement element : trainingMessages){ String link = element.getText(); System.out.println("這是留言的資料" + link); } }else{ WebElement conclusionMessage = driver.findElement(By.className("conclusionMessage")); System.out.println(conclusionMessage.getText()); } ``` # 16 JUnit and Selenium {%youtube 9v9LCcVvZ7I%} Selenium and JUnit are a natural fit for one another. Both are plain Java libraries, and don't require any special syntax or approach to integrate with one another. We can use Selenium's driver to navigate the web, interact with elements on the page, and extract data from those elements, and we can use JUnit's assertions to check the data that is returned against expected values. Selenium also requires some initialization logic, like setting up the web driver and navigating to the correct URL to perform further actions on. JUnit's `@BeforeAll` annotation is perfect for writing a method to initialize the web driver, and we can use the `@BeforeEach` annotation to write a method that navigates to a common starting URL for all tests in the class. Finally, since we need to make sure we quit the web driver once our tests are finished, we can use JUnit's `@AfterAll` annotation to define a method that takes care of that. Selenium provides another useful tool for JUnit test organization - the Page Object. A Page Object is a Java class that is meant to represent a specific web page under test. We can use Page Objects to reduce boilerplate when writing Selenium scripts, and, as we'll see in the next video, we can even use them to make our test code resemble the user stories under test. {%youtube 6Nfl3MMBm-I%} [For the full lecture sample sample code from the previous videos, click here](https://github.com/udacity/nd035-c1-spring-boot-basics-examples/tree/master/udacity-jwdnd-c1-l5-junit-and-selenium-master) In the previous video, we looked at a simple counter application, with some text to display the current count, an increment button, and a reset button. Our goal is to write some JUnit and Selenium code to test that all of the essential features of the app are functioning correctly. First, though, we want a Selenium Page Object to represent the page we're testing. Here's the full `CounterPage` class from the example: ```typescript public class CounterPage { @FindBy(id = "count-display") private WebElement countDisplay; @FindBy(id = "increment-button") private WebElement incrementButton; @FindBy(id = "reset-value-field") private WebElement resetValueField; @FindBy(id = "reset-button") private WebElement resetButton; //We call a static method from the PageFactory class called initElements //傳入兩個參數driver, this: this代表我們正在傳遞正在構造的計數器頁面的實例 //簡單來說這個方法會找網頁上的任何物件,並且把物件放到放面的@FindBy屬性裡面 //driver: driver會收到並且發布到那些被@FindBy()的elements public CounterPage(WebDriver driver) { PageFactory.initElements(driver, this); } //他會取得displaycount public int getDisplayedCount() { return Integer.parseInt(countDisplay.getText()); } //他可以觸發+1的相關事件 public void incrementCount() { incrementButton.click(); } //可以觸發reset的相關訊息 public void resetCount(int value) { //先把rest input number的地方清空 resetValueField.clear(); //然後把傳進來的參數放進去網頁的reset value裡面 resetValueField.sendKeys(String.valueOf(value)); //然後點擊reset button resetButton.click(); } } ``` There are three main sections to this, and any, Page Object: **Defining Element Selectors** ```typescript @FindBy(id = "count-display") private WebElement countDisplay; @FindBy(id = "increment-button") private WebElement incrementButton; @FindBy(id = "reset-value-field") private WebElement resetValueField; @FindBy(id = "reset-button") private WebElement resetButton; ``` The goal of a Page Object is to simplify and abstract away common Selenium tasks, like finding elements on the page. Previously, we did this with `driver.findElement` and `driver.findElements`, but in a Page Object, we can take a much more Spring-like approach by declaring annotated fields representing the elements we want to capture on the page. These element selectors will be automatically processed by Selenium, but we have to kick that process off ourselves - which we do in the next section: **Initializing Elements in the Constructor** ```typescript public CounterPage(WebDriver driver) { PageFactory.initElements(driver, this); } ``` In this example, we declare a WebDriver as the only constructor argument, and we call `PageFactory.initElements()` with the driver and the `this` keyword as arguments. This is shorthand to tell Selenium to use the given driver to initialize the `@FindBy`-annotated fields in the class. In principle, we could do this somewhere else, but as we'll see in the next video, initializing a Page Object in its constructor like this is pretty flexible and clean. By adding this constructor, whenever we create a new `CounterPage` object, Selenium will automatically find and capture the elements we declared, reducing a bunch of similar calls to `driver.findElement` to a single `new CounterPage()` instantiation. Once we have those elements, we can move on to the next section: **Creating Helper Methods** ```typescript public int getDisplayedCount() { return Integer.parseInt(countDisplay.getText()); } public void incrementCount() { incrementButton.click(); } public void resetCount(int value) { resetValueField.clear(); resetValueField.sendKeys(String.valueOf(value)); resetButton.click(); } ``` Now that our Page Object has selected elements from the page it represents, we can define helper methods that encapsulate common tasks for the page. In this counter example, we need to be able to read the current count from the screen, we need to be able to increment the count, and we need to reset the count. Notice that I didn't mention any specific elements to describe the functionality of these actions - while we have to be specific in our implementation of these methods, as you can see in the code above, the goal of writing these helpers is to separate the action taken on the class from the specific element interactions required to fulfill that action. In some ways, this is another instance of separation of concerns - by hiding the implementation details in these methods, if the HTML of the page ever changes, we don't have to update anything except the code inside this class - the tests that will use this class can just continue to call the same methods they did before. Speaking of tests - now that we've set up the CounterPage class, we can finally implement some tests for this app {%youtube aqv3XHHqsxo%} [For the full lecture sample code from the previous videos, click here](https://github.com/udacity/nd035-c1-spring-boot-basics-examples/tree/master/udacity-jwdnd-c1-l5-junit-and-selenium-master) Here's the full JUnit test class from the previous video: ```typescript //@SpringBootTest代表著允許Spring Start我們的application when the tests are run //裡面的參數代表: specifies that the server should be run on a random port //因為有可機率 another instance of the server might be running at the same time @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class L5Part16JUnitAndSeleniumApplicationTests { //協助我們取得Random Port 才可以driver.get正確的URL @LocalServerPort private Integer port; private static WebDriver driver; private CounterPage counter; @BeforeAll public static void beforeAll(){ WebDriverManager.chromedriver().setup(); driver = new ChromeDriver(); } @AfterAll public static void afterAll(){ driver.quit(); } @BeforeEach public void beforeEach(){ driver.get("http://localhost:" + port + "/counter"); //把driver放進去counterpage他會自動去網頁找object並填入CounterPage的屬性中 counter = new CounterPage(driver); } @Test public void testIncrement(){ //先取得網頁上面目前的count是多少 int preValue = counter.getDisplayCouont(); //click 新增buttom counter.incrementCount(); //重新取得counter裡面的count目前數字是多少 以確認是不是真的有+1 assertEquals(preValue + 1, counter.getDisplayCouont()); } @Test public void testIncrementTenTimes(){ int prevValue = counter.getDisplayCouont(); for(int i = 0; i < 10; i++){ assertEquals(prevValue+i, counter.getDisplayCouont()); counter.incrementCount(); } } @Test public void testReset(){ //selenium會清空input 然後填入10 然後click reset buttom counter.resetCount(10); //要確認reset之後是否真的回到10這個數字 assertEquals(10, counter.getDisplayCouont()); //測試看看如果輸入0呢? counter.resetCount(0); assertEquals(0, counter.getDisplayCouont()); } } ``` There are a few things we have to do to set up a test file for a Spring Boot app. The main thing is that we have to make sure our server is running before the tests start - we do that here with `@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)`. This tells JUnit to run the application before any tests are executed, with a random port number instead of the default `8080`. This is useful because it means we can have multiple copies of the app running at the same time, which is common in development and testing environments. Of course, we need to know what the random port ends up being so that we can use Selenium's `driver.get()` method to navigate the browser to our app. Spring makes this easy for us with the `@LocalServerPort` annotation. Spring will inject the current port into a field annotated with this like the example above. As we mentioned in the video, we set up the Selenium driver in an @BeforeAll method, and we quit it in an `@AfterAll` method. However, the magic really starts with the `@BeforeEach` method - here, we navigate to the `/countert` URL and initialize a new `CounterPage` object. This means that every test will start from this URL and with a fresh `CounterPage` object - which makes test development extremely simple. As you can see from the rest of the tests, we simply use the helper methods we defined on `CounterPage` to perform all actions in and retrieve all data from the browser. This makes our test code highly legible, and each test starts to look a lot like a user story - for example, for increment, we could read the test as **As a user, I can increment the count in order to see the displayed count increase by one** And the code doesn't look far off from that statement! That's a truly powerful abstraction. **Key Terms** * Page Object: a special POJO variant that can be defined for use with Selenium. A Page Object should have `@FindBy`-annotated fields that represent the key HTML elements under test, and should have helper methods that define high-level utilities and user actions on the page under test. **Further Research** * [Official Selenium Guide to Using Aage Objects](https://www.selenium.dev/documentation/en/guidelines_and_recommendations/page_object_models/) # 17 JUnit and Selenium Quizzes Imagine we’re creating a JUnit for the exercise we did earlier with our /animals page. We want to submit the form multiple times and make sure the message changes when we get to our 5th submission. Which JUnit and Selenium components can help us write this test? (0) Selenium Page Object Model (0) JUnit Assertions (0) Selenium`WebDriver.get` (X) Selenium `By.cssSelector` * We don't actually need to look anything up by css on this page, which is ideal! (X) JUnit `@AfterEach` * In this particular case, we don't have any behavior we need to do between tests. # 18 Edge Case: Page Load Times {%youtube AQgVt_WmLEA%} In the real world, things get complicated, fast. Nowhere is this more apparent than when trying to account for page load times when automating user testing. On the web, page load times can vary wildly according to different internet providers, the size of the resources a page has to load, the speed at which the server handles requests, and so on. It's virtually impossible to predict exactly when a page will load, and this presents a problem for testing; if we ask Selenium to find an element on a page before the page finishes loading, it's going to fail and we're going to have something like a big, fat `NullpointerException` on our hands. So how do we make sure an element is on the page before we ask Selenium to look for it? The answer is to use a `WebDriverWait`, which is a class Selenium provides just for this purpose. Let's look at the following code: ```typescript WebDriverWait wait = new WebDriverWait(driver, 1000); //透過WebDriverWait.until方法 直到找到element出現或是時間過期 才繼續執行 WebElement marker = wait.until(webDriver -> webDriver.findElement(By.id("page-load-marker"))); ``` In this example, we create a new `WebDriverWait` instance using a driver and a timeout in milliseconds. `WebDriverWait` defines a method called `until` that we use in the next line to force Selenium to pause until the specified element is found, or the timeout is reached. This is extremely handy, since we can now ensure that Selenium waits and continues in a structured way. **Further Research** * [Official Selenium Guide to Waits](https://www.selenium.dev/documentation/en/webdriver/waits/) # 19 Final Review **Exercise: Final Review** For this final final review, you're going to add automated user testing with JUnit and Selenium to your chat application. The goal is to test at least one end-to-end user action flow: using Selenium, automate the browser to register a new user, log in as that user, submit a chat message as that user, and then verify that the newly-submitted message shows up on the page with the correct message text and username attached. In order to accomplish this, you'll have to do a couple of things: **Add Selenium and WebDriverManager dependencies to the project**. JUnit is always included in a generated Spring Boot project by default, but Selenium and WebDriverManager are not. Add the following dependencies to your project `pom.xml` file: ```typescript <dependency> <groupId>io.github.bonigarcia</groupId> <artifactId>webdrivermanager</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> </dependency> ``` **Create Page Objects for the HTML templates involved in the tests**. In order to test our registration/login/message submission flow, we should implement separate Page Objects for the `signup.html`, `login.html`, and `chat.html` templates. You can structure them according to our examples this lesson if you'd like, or, if you have an idea for how to organize them you'd like to try out, you can do that. Page Objects are flexible and are meant to make your life easier, so if you can think of features for these Page Objects that would be helpful to you, go for it! **Write at least one high-level JUnit test that tests a new user's registration, login, and first message**. Using the page objects you created, write a high-level JUnit test that registers a new user, logs that user in, submits a new message as that user, and asserts that the username and text of the message that appears on the screen matches the new user's username and the text they submitted. You will also have to write the Spring Boot testing and web driver setup code in the JUnit test class that you're working in - you can refer back to our examples this lesson to see how we did it before. **[BONUS] Write another high-level test that verifies that two users can chat with each other and receive the identical messages on each end**. This requires that you completed the bonus task from last lesson, implementing a logout button. A chat application is pretty lonely when only one person is using it, and adding more users to any application is likely to reveal some unexpected bugs. Write a test that verifies that your app can support multiple users! **Run your tests and verify that the app functions as expected; if it doesn't, identify the problem and solve it, and verify your solution by re-running the tests**. This should be self-explanatory - the point of writing a test is to find errors in your code. If you find some, fix them, re-running your tests and re-attempting to fix errors until all tests are passing. >Final Review Exercise It's time to add automated user testing to our chat application! Update your previous final review project according to the tasks below, and refer back to the instructions above for the high-level goals and code snippets to include in your project. > * Add Selenium and WebDriverManager dependencies to the project > * Create Page Object for the HTML templates involved in the tests. > * Write at least one high-level JUnit test that tests a new user's registration, login, and first message. > * Run your tests and verify that the app functions as expected; if it doesn't, identify the problem and slove it, and verify you solution by re-running the test # 20 Solution: Final Review [For the full final review solution code, click here](https://github.com/udacity/nd035-c1-spring-boot-basics-examples/tree/master/udacity-jwdnd-c1-l5-final-review-solution-master) For this solution explanation, we're going to focus on the actual JUnit test that I asked you to implement. Since you had a lot of flexibility in how you could implement parts of this review, like the Page Objects, I won't be talking about them here. If you want to see the Page Objects and other code I wrote for this solution, click the link above. Here's the final JUnit test method from my solution: ```typescript @Test public void testUserSignupLoginAndSubmitMessage() { String username = "pzastoup"; String password = "whatabadpassword"; String messageText = "Hello!"; driver.get(baseURL + "/signup"); SignupPage signupPage = new SignupPage(driver); signupPage.signup("Peter", "Zastoupil", username, password); driver.get(baseURL + "/login"); LoginPage loginPage = new LoginPage(driver); loginPage.login(username, password); ChatPage chatPage = new ChatPage(driver); chatPage.sendChatMessage(messageText); ChatMessage sentMessage = chatPage.getFirstMessage(); assertEquals(username, sentMessage.getUsername()); assertEquals(messageText, sentMessage.getMessageText()); } ``` As you can see, the abstraction of Page Objects makes this test very readable and simple to follow. First, we use the driver to navigate to the signup page. We initialize a `SignupPage` object and call the `signup` method with some user account details - in my Page Objects, every top-level user action is represented by a single, simple method like this. For the sake of simplicity, we're assuming that each form submission is a success, so we then navigate to the login page and create another Page Object for that screen. We log in with the same credentials we registered, and since our Spring Security configuration is set up to automatically forward us to the `/chat` URL on successful login, we don't have to navigate with driver.get again. We then instantiate a `ChatPage` object, use its `sendChatMessage` method to send a new message to the chat, and the retrieve the sent message data from the browser. Notice that we're using the same `ChatMessage` class we're using throughout the application - don't be afraid to reuse POJOs like this when it fits the situation! Finally we check that the username and message text in the `ChatMessage` matches what we submitted, and we're done! **Glossary** * **Test Driven Development**: a software development methodology that emphasizes writing tests before the code to be tested. This gives developers a roadmap to success - once all the tests are passing, the feature is complete! * **User Story**: User stories are short sentences derived from feature requirements in the format of As a user, I can in order to . These are used to create tests to verify the components of a feature. * **Unit Tests**: A unit test only validates the smallest unit of a computational process. That might mean a test of a single method, or a single component in an application. * **Invariants**: An invariant is a law of computation, something that shouldn't change despite changing circumstances. For example, adding 0 to a number should always result in the original number, and dividing by 0 should always result in an error. * **Integration Tests**: Integration tests are intended to validate the operation of multiple application components as they interact with each other - or integrate with one another. * **Assertion**: an assertion, in the context of JUnit, is a method we can call to check our assumptions about the behavior of the unit under test. If our assumptions are correct, the assertion silently returns and the test method continues. If they're false, the assertion throws a special exception class that JUnit uses to build the final failure report, and the test method halts execution. * **Interactive Reporting**: When we run tests in an IDE, we can usually inspect the results of each test individually. If an assertion fails or an unexpected exception is triggered, the stack trace and circumstances will be shown in the details for each test, and clickable links in the results help you navigate to problem areas in your code. * **Interactive Debugging**: When a pernicious problem persists, it can often be helpful to step through the code's execution line-by-line to inspect both the control flow and the values in memory used by the program. This is called debugging, and while it's technically possible to do outside of an IDE, IDEs like IntelliJ provide many useful tools for making the process as painless as possible. * **Code Coverage Reports**: When we run code in an IDE like IntelliJ, we can choose to have the IDE track which lines of our code were visited, and how many times. This can be wildly useful when trying to track down why a branch of a condition isn't being reached, as well as when determining how much the entire code base is covered by the currently-implemented tests. * **Web Driver**: In order for Selenium to assume control of a browser, it needs a program to interface with the specific browser's API. This program is called a web driver, and there are different web drivers for each major browser. * **Page Object**: a special POJO variant that can be defined for use with Selenium. A Page Object should have `@FindBy`-annotated fields that represent the key HTML elements under test, and should have helper methods that define high-level utilities and user actions on the page under test. # 21 Lesson Conclusion {%youtube Lw0BdKMpdmQ%} In this lesson, you learned about test-driven development and user stories, JUnit and IntelliJ's tools for test running, and how to use Selenium to simulate a user's actions in an automated browser. Learning how to use these tools allowed you to write automated tests for the chat application you've been building this whole course, and with the rest of the skills you've acquired so far, you're ready to take on a larger, more complicated project and still feel confident that it is robust. More on that in the final project instructions. # 22 Course Recap {%youtube iDkcmoboXs0%} **Look How Far You've Come!** ![](https://i.imgur.com/eueGpXg.png) # 23 Congratulations! {%youtube FZTGXlHWsgk%} You've worked so hard, and you should be proud of all of the work you've done. You're on your way to becoming a Java Web Developer!