Skip to main content

Tools & Testing 工具与测试

CI/CD

1. What is Continuous Integration 持续集成?

Continuous integration 持续集成 The automated building(compile) and testing of your application on every new commit.

每次新的代码提交时,自动打包、编译、和测试。优点是更稳定和可靠的。

广义上只有三步: Build -> Test -> Artifact

2. What is Continuous Delivery 持续交付?

Continuous delivery 持续交付 A state where your application is always ready to be deployed. A manual step is required to actually deploy the application.

It's a practice ensures our application is always ready to be deployed and delivered to our users. 这是一种确保我们的应用程序始终准备好部署并交付给用户的最佳实践

  • Fast release 快速发布
  • reduce risk 减少风险
  • High Quality 高质量

Continuous deployment 持续部署 The automation of building, testing, and deploying. If all tests pass, every new commit will push new code through the entire development pipeline to production with no manual intervention.

2. What is Jenkins

Jenkins is an open-source Continuous Integration server used for automation. Jenkins supports the complete development life cycle of software from building, testing, documenting, and deploying.

3. 业界实践

  • CI/CD in DevOps is about unifying process and automation, it helps to act as a bridge between Development and Operations teams to make the process more reliable and adaptive.
  • Jenkins
  • AWS CodePipeline
  • Github Actions

Walmart

Concord is a workflow server. 工作流引擎。 Concord is a workflow engine that allows the submission of various CI/CD tasks, integrates all Jenkins features, and enables the submission of different orchestration tasks

OneOps is a cloud management and application lifecycle management platform. 多云管理和应用程序生命周期管理平台。 OneOps let developers use to both develop and launch new products faster, and to more easily maintain them throughout their entire lifecycle

4. How do you use CI/CD in your project?

  • We need a workflow platform to run different tasks, including packaging, building and more, which can use a declarative language based on YAML or JSON to define workflows.

  • CI: 需要工作流平台来执行各种任务,功能包括打包、构建

  • We also need a multi-cloud and container management platform for defining server or container. The platform will provide deploying, updating certificates, health checks, automatic replacement, and scaling functions, typically it’s configured using a scripting language.

  • CD: 需要一个多云和容器管理平台,功能包括部署、证书更新、健康检查、自动替换和 scale 功能

两者都使用 脚本/yaml 语言来配置

5. 请描述一次 Continuous integration 持续集成的过程

  • General speaking there are only three steps: Build -> Test -> Artifact
  • We will define a CI pipeline configured through a yaml file. 通过 yaml 文件来配置 pipeline
  • Once we have a PR, the CI pipeline will be triggered. 触发打包和构建过程
  • Initially, it will trigger the packaging and building process, then run all unit tests, generating results and coverage reports, and Finally the build will be completed. We’ll get the artifact and snapshot version. 运行所有单元测试,生成结果和覆盖率报告。最后完成构建,我们将获得工件和快照版本

Testing 测试

1. How do you do the testing in your project?

Load Test

  • By using use Jmeter or similar platform, We will set up our job, configing API endpoint, user numbers, test duration, and some custom fields, then submit the job to run on our testing server.
  • 配置 Job 的 API、用户数量、时间、custom configs,然后提交任务到测试服务器

Canary Release 金丝雀发布/小流量测试

  • We use an API gateway and configuration center to define traffic. 我们使用 API 网关和配置中心来定义流量。
  • When we want to release a new big feature, we’ll go into a low-traffic environment, such as 1%-5%. When our monitoring shows no issues with the traffic, we gradually increase it until it reaches 100%.

Integration Testing 集成测试

  • JUnit and Spring-Boot build-in testing feature: TestRestTemplate
  • Selenium is used for UI and Scenario testing UI 和场景测试

2. Production Testing 生产环境测试

  • Testing in production (TIP) means that new code changes are tested on live user traffic rather than in a staging environment. It is one of the testing practices used by continuous delivery.
  • The only way to know if the new code work in production is to test them in production.
  • 业界实践
    • 蓝绿部署. 是在有两个一样环境的前提下,不停老版本,部署新版本进行测试。测试没问题之后直接把流量切到新版本上,再把老版本也升级到新版本。一般适用于对用户体验有一定的忍耐度、机器资源丰富的团队。
    • A/B Testing. 主要用于产品功能对比,版本 A 和版本 B 分别部署在不同的服务器上并开放给不同的用户使用,一般适用于收集用户反馈辅助产品功能设计。

3. JUnit - How to test a static method?

@Slf4j
public class Utility {

private static final int MIN_USERNAME_LENGTH = 5;
private static final int MAX_USERNAME_LENGTH = 20;

public static int add(int a, int b) {
return a + b;
}

// Validates username based on certain criteria.
public static boolean isValidUsername(String username) {
if (username == null) {
return false;
}

// Check length constraints
if (username.length() < MIN_USERNAME_LENGTH || username.length() > MAX_USERNAME_LENGTH) {
log.info("username length is not valid: " + username.length());
return false;
}

// Ensure username only contains alphanumeric characters
for (char c : username.toCharArray()) {
if (!Character.isLetterOrDigit(c)) {
log.info("username contains non-alphanumeric characters: " + c);
return false;
}
}
return true;
}
}
@Slf4j
public class UtilityTest {

@Test
public void testAdd() {
int ans = Utility.add(2, 3);
log.info("ans = {}", ans);
assertEquals(5, ans);
}

@Test
public void testIsValidUsername() {
assertTrue(Utility.isValidUsername("JohnDoe123"));
assertFalse(Utility.isValidUsername("John")); // Too short
assertFalse(Utility.isValidUsername("JohnDoe123456789012345")); // Too long
assertFalse(Utility.isValidUsername("John@Doe")); // Contains non-alphanumeric characters
}
}

4. JUnit - How to test a method which is using External Service?

Model layer
@Data
@AllArgsConstructor
public class Book {
private String id;
private String title;
private String author;
}
Service layer
@Slf4j
@Data
public class BookService {

private static final Logger logger = LoggerFactory.getLogger(BookService.class);
private ExternalBookService externalBookService;

public BookService(ExternalBookService externalBookService) {
this.externalBookService = externalBookService;
}

public Book getBookDetails(String id) {
try {
logger.info("Fetching details for Book ID: {}", id);
return externalBookService.fetchBookByID(id);
} catch (Exception e) {
logger.error("Error fetching details for Book ID: {}", id, e);
throw new RuntimeException(e);
}
}
}
ExternalBookService.java
public class ExternalBookService {

public Book fetchBookByID(String id) {
return new Book(
id,
"Effective Java",
"Lucas Hu"
);
}
}
BookServiceTest.java
@Slf4j
public class BookServiceTest {

private BookService bookService;
private ExternalBookService externalBookServiceMock;

@BeforeEach
public void setup() {
// 1. Create a mock object of ExternalBookService
externalBookServiceMock = Mockito.mock(ExternalBookService.class);
// 2. create an instance of service and inject the mock to it.
bookService = new BookService(externalBookServiceMock);
}

@Test
public void testGetBookDetails() {
String id = "1234567890";
// 3. define the return value for method fetchBookByID()
Book mockBook = new Book(id, "Mock Title", "Mock Author");
log.info("mockBook from externalBookServiceMock = {}", mockBook);

// 4. define mockBook to be returned by mock
when(externalBookServiceMock.fetchBookByID(id)).thenReturn(mockBook);

// 5. call the method from bookService to get actual result
Book result = bookService.getBookDetails(id);
log.info("result from bookService = {}", result);

// 6. Assertions to verify that the mock method returned the expected value
assertEquals(mockBook.getId(), result.getId(), "Book ID does not match.");
assertEquals(mockBook.getTitle(), result.getTitle(), "Book title does not match.");
assertEquals(mockBook.getAuthor(), result.getAuthor(), "Book author does not match.");

// 7. verify the method fetchBookByID() is called once
// 确保调用了 Ensure we called the method
// 确保没有重复调用 Ensure we did not call it more than once

verify(externalBookServiceMock, times(1)).fetchBookByID(id);
}
}

Tools 工具

1. Have you worked on cron jobs?

  • Quartz Scheduler:Quartz 是一个功能丰富且强大的定时任务库,可以用于创建简单或复杂的定时任务。它是开源的,支持 Cron 表达式,并且可以在 Java 应用程序中很好地集成。 Quartz Scheduler Website
// 1. 添加依赖

// 2. 创建一个实现 org.quartz.Job 接口的任务类,并在 execute 方法中实现具体的任务逻辑:
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class MyJob implements Job {

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Executing MyJob...");
// 在这里编写你的任务逻辑
}
}

// 3. 创建一个调度器实例,并配置任务、触发器以及调度任务:
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzDemo {

public static void main(String[] args) {
try {
// 创建调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

// 定义任务
JobDetail job = JobBuilder.newJob(MyJob.class)
.withIdentity("myJob", "group1")
.build();

// 定义触发器,使用 Cron 表达式
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")) // 每 5 秒执行一次
.build();

// 调度任务
scheduler.scheduleJob(job, trigger);

// 启动调度器
scheduler.start();

// 等待 60 秒
Thread.sleep(60000);

// 关闭调度器
scheduler.shutdown();

} catch (SchedulerException | InterruptedException e) {
e.printStackTrace();
}
}
}

// 在这个例子中,我们创建了一个调度器实例,定义了一个任务,使用 Cron 表达式创建了一个触发器(每 5 秒执行一次),并调度任务。调度器启动后,任务将根据触发器的设置执行。在这个例子中,我们让调度器运行 60 秒后关闭。
  • Spring Task Scheduler:Spring Task Scheduler 是 Spring 框架提供的一个简单但功能强大的任务调度框架。它支持固定频率、固定延迟和 Cron 表达式。Spring Task Scheduler 是一个轻量级的选择,特别是对于已经在使用 Spring 的项目。
    • Cron 源自 Unix/Linux 系统自带的 crond 守护进程,以一个简洁的表达式定义任务触发时间。在 Spring 中,也可以使用 Cron 表达式来执行 Cron 任务,在 Spring 中,它的格式是: 秒 分 小时 天 月份 星期 年
    • 每天凌晨 2:15 执行的 Cron 表达式就是: 0 15 2 * * *

// 1. 修改 Spring 启动类,增加线程池配置
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class DistributedJobApplication {

public static void main(String[] args) {
SpringApplication.run(DistributedJobApplication.class, args);
}

@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
return taskScheduler;
}
}

// 2. 模拟多线程任务调度

@Scheduled(fixedRate = 3000)
public void process1() throws InterruptedException {
log.info("process1...start");
Thread.sleep(50000);
log.info("process1...stop");
}

@Scheduled(fixedRate = 3000)
public void process2() throws InterruptedException {
log.info("process2...start");
Thread.sleep(50000);
log.info("process2...stop");
}

2023-02-14 00:30:19.477 INFO 21221 --- [taskScheduler-1] com.imooc.distributedjob.MyJob : process1...start
2023-02-14 00:30:19.477 INFO 21221 --- [taskScheduler-2] com.imooc.distributedjob.MyJob : process2...start

2023-02-14 00:30:24.477 INFO 21221 --- [taskScheduler-1] com.imooc.distributedjob.MyJob : process1...stop
2023-02-14 00:30:24.477 INFO 21221 --- [taskScheduler-2] com.imooc.distributedjob.MyJob : process2...stop