This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler-website.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new 2139bd5  Automated deployment: 2a1d79140e34611e525d8e6f20dc70b8c7401600
2139bd5 is described below

commit 2139bd5142e7accdd14f136f93abfafc5bd84200
Author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
AuthorDate: Sun Sep 26 10:35:53 2021 +0000

    Automated deployment: 2a1d79140e34611e525d8e6f20dc70b8c7401600
---
 zh-cn/blog/ut-template.html | 436 ++++++++++++++++++++++++++++++++++++++++++++
 zh-cn/blog/ut-template.json |   6 +
 2 files changed, 442 insertions(+)

diff --git a/zh-cn/blog/ut-template.html b/zh-cn/blog/ut-template.html
new file mode 100644
index 0000000..bd0f85d
--- /dev/null
+++ b/zh-cn/blog/ut-template.html
@@ -0,0 +1,436 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, 
maximum-scale=1.0, user-scalable=no">
+  <meta name="keywords" content="ut-template">
+  <meta name="description" content="ut-template">
+  <title>ut-template</title>
+  <link rel="shortcut icon" href="/img/favicon.ico">
+  <link rel="stylesheet" href="/build/vendor.e328afe.css">
+  <link rel="stylesheet" href="/build/blog.md.fd8b187.css">
+</head>
+<body>
+  <div id="root"><div class="blog-detail-page" data-reactroot=""><header 
class="header-container header-container-dark"><div class="header-body"><a 
href="/zh-cn/index.html"><img class="logo" src="/img/hlogo_white.svg"/></a><div 
class="search search-dark"><span class="icon-search"></span></div><span 
class="language-switch language-switch-dark">En</span><div 
class="header-menu"><img class="header-menu-toggle" 
src="/img/system/menu_white.png"/><div><ul class="ant-menu whiteClass 
ant-menu-li [...]
+<h2>1. 单元测试原则</h2>
+<ol>
+<li>
+<p><strong>3A 原则</strong></p>
+<p>3A 
原则简单易懂,它为套件中的所有测试提供了统一的结构,这种统一的结构是其最大的优势之一:一旦习惯了这种模式,就可以更轻松地阅读和理解测试,这反过来又降低了整个测试套件的维护成本。</p>
+<ul>
+<li>Arrange:初始化测试数据。</li>
+<li>Act:调用被测方法,传入依赖参数并获取返回值。</li>
+<li>Assert:断言,对返回值做出断言。</li>
+</ul>
+<p>示例:</p>
+<pre><code class="language-java"><span class="hljs-keyword">public</span> 
<span class="hljs-class"><span class="hljs-keyword">class</span> <span 
class="hljs-title">Calculator</span> </span>{
+    <span class="hljs-function"><span class="hljs-keyword">public</span> <span 
class="hljs-keyword">long</span> <span class="hljs-title">sum</span><span 
class="hljs-params">(<span class="hljs-keyword">long</span> a, <span 
class="hljs-keyword">long</span> b)</span> </span>{
+        <span class="hljs-keyword">return</span> a + b;
+    }
+}
+</code></pre>
+<pre><code class="language-java"><span class="hljs-keyword">public</span> 
<span class="hljs-class"><span class="hljs-keyword">class</span> <span 
class="hljs-title">CalculatorTest</span> </span>{
+    <span class="hljs-meta">@Test</span>
+    <span class="hljs-function"><span class="hljs-keyword">public</span> <span 
class="hljs-keyword">void</span> <span class="hljs-title">sum</span><span 
class="hljs-params">()</span> </span>{
+        <span class="hljs-comment">// Arrange</span>
+        <span class="hljs-keyword">long</span> a = <span 
class="hljs-number">1L</span>, b = <span class="hljs-number">2L</span>;
+        Calculator calculator = <span class="hljs-keyword">new</span> 
Calculator();
+        <span class="hljs-comment">// Act</span>
+        <span class="hljs-keyword">long</span> actual = calculator.sum(a, b);
+        <span class="hljs-comment">// Assert</span>
+        <span class="hljs-keyword">long</span> expected = <span 
class="hljs-number">3L</span>;
+        assertEquals(expected, actual);
+    }
+}
+</code></pre>
+</li>
+<li>
+<p><strong>AIR 原则</strong></p>
+<ul>
+<li>Automatic:测试过程应当是完全自动的、非交互的。</li>
+<li>Independent:为了保证单元测试稳定可靠且便于维护,单元测试用例之间不允许互相调用,也不能依赖执行的先后次序。</li>
+<li>Repeatable:单元测试是可以重复执行的,不能受到外界环境的影响。</li>
+</ul>
+</li>
+</ol>
+<p>除此以外,还应遵循以下原则:</p>
+<ol>
+<li><strong>隔离性与单一性</strong></li>
+</ol>
+<p>一个测试类应该只对应于一个被测试类,并且对被测试类行为的测试环境应该是隔离的。</p>
+<p>一个测试用例应该精确到方法级别,并应该能够单独执行该测试用例,同时关注点也始终在该方法上。</p>
+<p>如果方法过于复杂,开发阶段就应该将其再次进行拆分,对于测试用例来讲,最佳做法是一个用例只关注一个分支(判断)。当对其进行修改后,也仅仅影响一个测试用例的成功与否。这会极大方便我们在开发阶段验证问题和解决问题,但与此同时,也对我们覆盖率提出了极大的挑战。</p>
+<ol start="2">
+<li><strong>可重复性</strong></li>
+</ol>
+<p>在任何环境、任何时间,多次执行后的结果一致,且可以重复执行。</p>
+<ol start="3">
+<li><strong>轻量型</strong></li>
+</ol>
+<p>测试应当是秒级甚至是毫秒级的,不应占用过多时间。</p>
+<blockquote>
+<p>由于 Spring Boot 启动花费时间较长,因此当待测试对象不依赖 Spring Bean 或 Spring 容器时,应当避免使用 Spring 
Test 进行单元测试,可以通过直接创建目标类对象的方式实现。</p>
+</blockquote>
+<ol start="4">
+<li><strong>可测性</strong></li>
+</ol>
+<p>为了保证每个软件组件的正确性,我们希望将代码的质量保障提前,使每个软件组件在开发阶段就能够测试,因此在设计和开发过程中,需要保证每个模块是可以进行测试的。</p>
+<p>为了保证可测性,设计和编写业务逻辑代码时需注意以下几点:</p>
+<ul>
+<li>依赖隔离。在编写业务逻辑代码时,应遵循依赖倒置原则——高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象,抽象不应该依赖于具体实现,具体实现应该依赖于抽象。通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并且能够降低修改程序所造成的风险。</li>
+<li>避免使用静态变量。静态变量是全局性的、有状态的,在多线程中处理复杂。单元测试应该彼此独立、隔离,不应该依赖于执行顺序,甚至应该允许并发同时执行所有单元测试进而使测试更快速。使用静态变量会破坏单元测试的可重复执行性,并且可能干扰到其他的单元测试方法。</li>
+<li>避免使用静态方法。一般建议只在一些工具类提供静态方法,这种情况下也不需要 
mock,直接使用真实类即可。如果被依赖类不是工具类,可以将静态方法重构为实例方法。这样更加符合面向对象的设计理念。</li>
+</ul>
+<ol start="5">
+<li><strong>完备性</strong></li>
+</ol>
+<p>测试覆盖率通常被用来衡量测试的充分性和完整性,核心流程期望达到 90% 的覆盖率,非核心流程期望达到 60% 以上的覆盖率。</p>
+<p>覆盖率足够高的情况下可以减少 bug 出现的概率,同时也减少了回归测试的成本。</p>
+<blockquote>
+<p>DolphinScheduler 使用 Sonar 存储和管理覆盖率等各项指标:<a 
href="https://sonarcloud.io/code?id=apache-dolphinscheduler";>https://sonarcloud.io/code?id=apache-dolphinscheduler</a></p>
+</blockquote>
+<ol start="6">
+<li><strong>拒绝无效断言</strong></li>
+</ol>
+<p>无效断言让测试本身变得毫无意义,它和你的代码正确与否几乎没什么关系,且有可能会给你造成一种成功的假象,这种假象有可能持续到你的代码部署到生产环境。</p>
+<p>无效断言类型:</p>
+<ul>
+<li>不同类型的比较。</li>
+<li>判断一个具有默认值的对象或者变量不为空。</li>
+</ul>
+<p>断言尽可能采用肯定断言而非否定断言,断言尽可能在一个预知结果范围内,或者是准确的数值(否则有可能会导致一些不符合你的实际预期但是通过了断言)除非你的代码只关心他是否为空。</p>
+<ol start="7">
+<li><strong>异常</strong></li>
+</ol>
+<p>对异常的验证是单元测试中一个很重要的环节,在编写单元测试时,除了正常的输入输出,还需要特别针对可能导致异常的情况进行测试。</p>
+<p>需注意以下几点:</p>
+<ol>
+<li>程序在测试过程中抛出异常,并不一定是 bug;程序应当抛出异常的地方却没有抛出异常,一定是 bug。</li>
+<li>在编写单元测试代码时,需要全面了解设计文档或业务代码,明确在什么情况下会抛出哪些异常,尽可能使单元测试能够覆盖更多的场景。</li>
+<li>在测试失败的代码块中通过 <code>Assert.fail(String message)</code> 声明测试失败情况。</li>
+<li>使用 <code>@Test(expected = RuntimeException.class)</code> 表明方法抛出的 
<code>RuntimeException</code> 是合法的,若抛出其他异常或不抛出异常都会使测试失败。</li>
+</ol>
+<h2>2. 单元测试编写建议</h2>
+<ol>
+<li>
+<p>应当保证单元测试的测试粒度足够小,有助于精确定位问题。单元测试粒度至多是类级别,一般是方法级别。</p>
+<blockquote>
+<p>只有测试粒度足够小,才能在出错时尽快定位到出错位置。除此以外,单元测试不负责检查跨类或者跨系统的交互逻辑(这是集成测试的领域)。</p>
+</blockquote>
+</li>
+<li>
+<p>单元测试代码应当放在 <code>src/test/java</code> 路径下。</p>
+</li>
+<li>
+<p>同一个单元测试方法中只能涵盖对同一类测试用例的测试,并且建议使用 
<code>test_MethodName_CaseDescription</code> 测试方法命名方式。</p>
+</li>
+<li>
+<p>编写单元测试代码应遵守 BCDE 原则,以保证被测试模块的交付质量。</p>
+<ul>
+<li>Border:边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。</li>
+<li>Correct:正确的输入,并得到预期的结果。</li>
+<li>Design:与设计文档相结合,来编写单元测试。</li>
+<li>Error:强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。</li>
+</ul>
+</li>
+<li>
+<p>对于数据库相关的增删改查等操作,不能假设数据库里的数据是存在的,或者直接向数据库中插入数据,应当采用程序插入或者导入数据的方式准备数据。</p>
+<blockquote>
+<p>例如,在删除某一行数据的单元测试中,若事先手动向数据库中增加一行数据作为待删除目标,但由于该行新增数据并不一定符合业务插入规范,因此有可能导致测试结果异常。</p>
+</blockquote>
+</li>
+<li>
+<p>数据库相关的单元测试可以设定自动回滚机制,避免数据库中存在脏数据。或者对单元测试产生的数据有明确的前后缀标识。</p>
+</li>
+<li>
+<p>为了更方便地进行单元测试,业务代码应避免出现以下情况:</p>
+<ol>
+<li>
+<p>构造方法中做的事情过多。</p>
+</li>
+<li>
+<p>存在过多的全局变量和静态方法。</p>
+</li>
+<li>
+<p>存在过多的外部依赖。</p>
+</li>
+<li>
+<p>存在过多的条件语句。</p>
+<blockquote>
+<p>多层条件语句可以使用卫语句、策略模式、状态模式等方式重构。</p>
+</blockquote>
+</li>
+</ol>
+</li>
+</ol>
+<h2>3. Mock</h2>
+<h3>3.1 为什么需要 mock?</h3>
+<p>单元测试要求在不涉及依赖关系的情况下测试代码,即可迁移性。</p>
+<p>模拟对象(Mock 
Object)可以取代真实对象的位置,用于测试一些与真实对象进行交互或依赖于真实对象的功能。模拟真实对象的目的就是创建一个轻量级的、可以控制的对象来代替测试中需要的真实对象,模拟真实对象的行为和功能。</p>
+<blockquote>
+<p>例如,service 调用 dao,即 service 依赖 dao,这时候可以用 mock 对象来模拟真实的 dao 调用,从而达到不依赖 dao 
具体实现逻辑的情况下测试 service 的目的,以减少模块间耦合。</p>
+</blockquote>
+<p><strong>mock对象使用范畴:</strong></p>
+<ol>
+<li>真实对象具有不可确定的行为,产生不可预测的效果。</li>
+<li>真实对象很难被创建的。</li>
+<li>真实对象的某些行为很难被触发。</li>
+<li>真实对象实际上并不存在的。</li>
+</ol>
+<blockquote>
+<p>因此,发送邮件、依赖 DAO 的上层操作(如 Service)、Controller 层 HTTP 请求等场景建议使用 mock 测试。</p>
+</blockquote>
+<h3>3.2 为什么应当避免使用 mock?</h3>
+<ol>
+<li>需要额外编写 mock 测试代码。</li>
+<li>掩盖实际代码的执行情况,可能无法完全覆盖测试场景。</li>
+<li>测试代码的可复用性差。</li>
+</ol>
+<h2>4. Controller 层测试</h2>
+<h3>4.1 编写建议</h3>
+<ol>
+<li>
+<p>为了使测试更加快速,测试时应尽量避免构建 Spring Context。</p>
+</li>
+<li>
+<p>通过构造函数配置依赖项。</p>
+<blockquote>
+<p>优点:</p>
+<ol>
+<li>允许将字段声明为 final。final 关键字会有助于性能提升,并且由于 final 变量是只读的,因此在多线程环境下无需额外的同步开销。</li>
+<li>避免通过 Spring 配置依赖项,使测试运行更快。</li>
+</ol>
+</blockquote>
+</li>
+<li>
+<p>建议使用 <code>MockMvc</code> 对象模拟 HTTP 请求实现 Controller 层测试。</p>
+<blockquote>
+<p>优点:</p>
+<ol>
+<li><code>MockMvc</code> 可以在不启动 Web 服务器和构建 Spring Context 的情况下实现 Controller 
测试。</li>
+<li><code>MockMvc</code> 提供了许多有用的、用于执行请求和断言结果的方法。</li>
+</ol>
+</blockquote>
+</li>
+</ol>
+<h3>4.2 编写示例</h3>
+<p>Controller UT 架构如下:</p>
+<p><img 
src="/home/shenke/Desktop/%E5%BC%80%E6%BA%90%E4%B9%8B%E5%A4%8F/dolphinscheduler/210290131/img/12.png"
 alt=""></p>
+<p>对于新增的 controller,其测试代码编写步骤如下:</p>
+<ol>
+<li>继承 <code>RestControllerTest</code> 或 <code>NormalControllerTest</code></li>
+<li>重写 <code>getTestedController()</code> 方法,返回新增的待测试 controller 对象</li>
+<li>基于 <code>MockMvc</code> 编写测试方法,模拟 HTTP 请求</li>
+</ol>
+<p>待测试方法:</p>
+<pre><code class="language-java"><span class="hljs-meta">@RestController</span>
+<span class="hljs-keyword">public</span> <span class="hljs-class"><span 
class="hljs-keyword">class</span> <span 
class="hljs-title">UserController</span> <span 
class="hljs-keyword">extends</span> <span 
class="hljs-title">BaseController</span> </span>{
+
+    <span class="hljs-keyword">private</span> <span 
class="hljs-keyword">final</span> UserService service;
+
+    <span class="hljs-function"><span class="hljs-keyword">public</span> <span 
class="hljs-title">UserController</span><span class="hljs-params">(UserService 
service)</span> </span>{
+        <span class="hljs-keyword">this</span>.service = service;
+    }
+
+    <span class="hljs-meta">@RequestMapping(&quot;/login&quot;)</span>
+    <span class="hljs-meta">@ResponseStatus(HttpStatus.OK)</span>
+    <span class="hljs-function"><span class="hljs-keyword">public</span> 
Result <span class="hljs-title">login</span><span class="hljs-params">(<span 
class="hljs-meta">@RequestParam(value = &quot;userName&quot;, required = 
false)</span> String userName, <span class="hljs-meta">@RequestParam(value = 
&quot;userPassword&quot;, required = false)</span> String userPassword)</span> 
</span>{
+
+        Map&lt;String, Object&gt; result = service.login(userName, 
userPassword);
+        <span class="hljs-keyword">return</span> returnDataList(result);
+    }
+
+}
+</code></pre>
+<p>测试代码:</p>
+<pre><code class="language-java"><span class="hljs-keyword">public</span> 
<span class="hljs-class"><span class="hljs-keyword">class</span> <span 
class="hljs-title">RestUserControllerTest</span> <span 
class="hljs-keyword">extends</span> <span 
class="hljs-title">AbstractRestControllerTest</span> </span>{
+
+    <span class="hljs-keyword">private</span> <span 
class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> 
Logger logger = LoggerFactory.getLogger(RestUserControllerTest.class);
+
+    <span class="hljs-meta">@Test</span>
+    <span class="hljs-function"><span class="hljs-keyword">public</span> <span 
class="hljs-keyword">void</span> <span class="hljs-title">testLogin</span><span 
class="hljs-params">()</span> <span class="hljs-keyword">throws</span> 
Exception </span>{
+        <span class="hljs-comment">// (2) Act</span>
+        MvcResult mvcResult = mockMvc.perform(post(<span 
class="hljs-string">&quot;/login&quot;</span>))
+                .andExpect(status().isOk())    <span class="hljs-comment">// 
(3) Assert</span>
+                .andReturn();
+
+        MockHttpServletResponse response = mvcResult.getResponse();
+        String content = response.getContentAsString();
+
+        Result result = JSONUtils.parseObject(content, Result.class);
+        <span class="hljs-comment">// (3) Assert</span>
+        Assert.assertEquals(Status.SUCCESS.getCode(), 
result.getCode().intValue());
+        logger.info(content);
+    }
+
+    <span class="hljs-meta">@Override</span>
+    <span class="hljs-function"><span class="hljs-keyword">protected</span> 
Object <span class="hljs-title">getTestedController</span><span 
class="hljs-params">()</span> </span>{
+        <span class="hljs-comment">// (1) Arrange</span>
+        UserService service = <span class="hljs-keyword">new</span> 
UserService();
+        <span class="hljs-keyword">return</span> <span 
class="hljs-keyword">new</span> UserController(service);
+    }
+
+}
+</code></pre>
+<h2>5. DAO 层测试</h2>
+<h3>5.1 编写建议</h3>
+<p>DAO 层测试有三种方案:</p>
+<ol>
+<li>使用 mock 对象模拟数据库操作</li>
+<li>使用内存数据库进行测试</li>
+<li>使用真实数据库环境进行测试</li>
+</ol>
+<p>三者对比如下:</p>
+<ul>
+<li>使用模拟对象有利于提高可测试性,避免环境对单元测试的限制。但是这种方案是建立在数据库操作都正确的假想下,因此并不能完全覆盖所有场景(如对 SQL 
语句执行结果的测试)。</li>
+<li>使用内存数据库进行测试同样避免了环境对单元测试的限制,同时使测试更可靠、更快速。</li>
+<li>使用真实数据库环境进行测试更具有可靠性,但是也对测试环境有了限制,降低了测试的可迁移性,同时数据库操作可能会使测试时间延长,并产生许多脏数据。</li>
+</ul>
+<p>综上,在涉及数据库的单元测试场景中,推荐使用前两种方案。</p>
+<h3>5.2 编写示例</h3>
+<p>待测试代码:</p>
+<pre><code class="language-java"><span class="hljs-meta">@Service</span>
+<span class="hljs-keyword">public</span> <span class="hljs-class"><span 
class="hljs-keyword">class</span> <span 
class="hljs-title">UsersServiceImpl</span> <span 
class="hljs-keyword">extends</span> <span 
class="hljs-title">BaseServiceImpl</span> <span 
class="hljs-keyword">implements</span> <span 
class="hljs-title">UsersService</span> </span>{
+    
+    <span class="hljs-comment">/**
+     * query user
+     *
+     * <span class="hljs-doctag">@param</span> name name
+     * <span class="hljs-doctag">@param</span> password password
+     * <span class="hljs-doctag">@return</span> user info
+     */</span>
+    <span class="hljs-meta">@Override</span>
+    <span class="hljs-function"><span class="hljs-keyword">public</span> User 
<span class="hljs-title">queryUser</span><span class="hljs-params">(String 
name, String password)</span> </span>{
+        String md5 = EncryptionUtils.getMd5(password);
+        <span class="hljs-keyword">return</span> 
userMapper.queryUserByNamePassword(name, md5);
+    }
+    
+}
+</code></pre>
+<h5>1. Mock</h5>
+<blockquote>
+<p>模拟 DAO 层数据库操作,实现 Service 层测试</p>
+</blockquote>
+<p>测试代码:</p>
+<pre><code class="language-java"><span 
class="hljs-meta">@RunWith(MockitoJUnitRunner.class)</span>
+<span class="hljs-keyword">public</span> <span class="hljs-class"><span 
class="hljs-keyword">class</span> <span 
class="hljs-title">UsersServiceTest</span> </span>{
+    
+    <span class="hljs-keyword">private</span> <span 
class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> 
Logger logger = LoggerFactory.getLogger(UsersServiceTest.class);
+
+    <span class="hljs-meta">@InjectMocks</span>
+    <span class="hljs-keyword">private</span> UsersServiceImpl usersService;
+
+    <span class="hljs-meta">@Mock</span>
+    <span class="hljs-keyword">private</span> UserMapper userMapper;
+    
+    <span class="hljs-meta">@Test</span>
+    <span class="hljs-function"><span class="hljs-keyword">public</span> <span 
class="hljs-keyword">void</span> <span 
class="hljs-title">testQueryUser</span><span class="hljs-params">()</span> 
</span>{
+        String userName = <span 
class="hljs-string">&quot;userTest0001&quot;</span>;
+        String userPassword = <span 
class="hljs-string">&quot;userTest0001&quot;</span>;
+        when(userMapper.queryUserByNamePassword(userName, 
EncryptionUtils.getMd5(userPassword))).thenReturn(getGeneralUser());
+        User queryUser = usersService.queryUser(userName, userPassword);
+        logger.info(queryUser.toString());
+        Assert.assertTrue(queryUser != <span class="hljs-keyword">null</span>);
+    }
+    
+    <span class="hljs-comment">/**
+     * get user
+     */</span>
+    <span class="hljs-function"><span class="hljs-keyword">private</span> User 
<span class="hljs-title">getGeneralUser</span><span 
class="hljs-params">()</span> </span>{
+        User user = <span class="hljs-keyword">new</span> User();
+        user.setUserType(UserType.GENERAL_USER);
+        user.setUserName(<span 
class="hljs-string">&quot;userTest0001&quot;</span>);
+        user.setUserPassword(<span 
class="hljs-string">&quot;userTest0001&quot;</span>);
+        <span class="hljs-keyword">return</span> user;
+    }
+
+}
+</code></pre>
+<h5>2. H2 内存数据库</h5>
+<ol>
+<li>
+<p>pom 依赖</p>
+<pre><code class="language-xml"><span class="hljs-tag">&lt;<span 
class="hljs-name">dependency</span>&gt;</span>
+    <span class="hljs-tag">&lt;<span 
class="hljs-name">groupId</span>&gt;</span>com.h2database<span 
class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
+    <span class="hljs-tag">&lt;<span 
class="hljs-name">artifactId</span>&gt;</span>h2<span 
class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
+    <span class="hljs-tag">&lt;<span 
class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span 
class="hljs-name">scope</span>&gt;</span>
+<span class="hljs-tag">&lt;/<span 
class="hljs-name">dependency</span>&gt;</span>
+</code></pre>
+</li>
+<li>
+<p>数据库初始化</p>
+<blockquote>
+<p>由于 h2 是内存数据库,不会持久化表结构,因此在每次测试前都要先初始化表结构。</p>
+</blockquote>
+<p>在 <code>test/resources</code> 目录下新建 <code>application.yml</code> 文件</p>
+<pre><code class="language-yaml"><span class="hljs-attr">spring:</span>
+  <span class="hljs-attr">database:</span>
+       <span class="hljs-attr">driver-class-name:</span> <span 
class="hljs-string">org.h2.Driver</span>
+       <span class="hljs-attr">url:</span> <span 
class="hljs-string">jdbc:h2:mem:test</span>   <span class="hljs-comment"># test 
为数据库名称</span>
+    <span class="hljs-attr">initialization-mode:</span> <span 
class="hljs-string">always</span>        <span class="hljs-comment"># always: 
每次启动时进行初始化</span>
+    <span class="hljs-attr">schema:</span> <span 
class="hljs-string">classpath:sql/schema.sql</span>   <span 
class="hljs-comment"># 用于初始化表结构的 sql 文件的路径</span>
+    <span class="hljs-attr">data:</span> <span 
class="hljs-string">classpath:sql/data.sql</span>       <span 
class="hljs-comment"># 用于初始化表数据的 sql 文件的路径</span>
+
+<span class="hljs-comment"># 打印 sql debug 日志</span>
+<span class="hljs-attr">logging:</span>
+  <span class="hljs-attr">level:</span>
+    <span class="hljs-attr">org.apache.dolphinscheduler.dao.mapper:</span> 
<span class="hljs-string">debug</span>
+</code></pre>
+</li>
+<li>
+<p>测试</p>
+<blockquote>
+<p>基于数据库环境进行测试,无需编写 mock 代码</p>
+</blockquote>
+<p>测试代码:</p>
+<pre><code class="language-java"><span 
class="hljs-meta">@RunWith(MockitoJUnitRunner.class)</span>
+<span class="hljs-keyword">public</span> <span class="hljs-class"><span 
class="hljs-keyword">class</span> <span 
class="hljs-title">UsersServiceTest</span> </span>{
+    
+    <span class="hljs-keyword">private</span> <span 
class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> 
Logger logger = LoggerFactory.getLogger(UsersServiceTest.class);
+
+    <span class="hljs-meta">@InjectMocks</span>
+    <span class="hljs-keyword">private</span> UsersServiceImpl usersService;
+
+    <span class="hljs-meta">@Mock</span>
+    <span class="hljs-keyword">private</span> UserMapper userMapper;
+    
+    <span class="hljs-meta">@Before</span>
+    <span class="hljs-function"><span class="hljs-keyword">public</span> <span 
class="hljs-keyword">void</span> <span 
class="hljs-title">createUser</span><span class="hljs-params">()</span> </span>{
+        String userName = <span 
class="hljs-string">&quot;userTest0001&quot;</span>;
+        String userPassword = <span 
class="hljs-string">&quot;userTest0001&quot;</span>;
+        User user = <span class="hljs-keyword">new</span> User();
+        user.setUserName(userName);
+        user.setUserPassword(userPassword);
+        userMapper.insert(user);
+    }
+    
+    <span class="hljs-meta">@Test</span>
+    <span class="hljs-function"><span class="hljs-keyword">public</span> <span 
class="hljs-keyword">void</span> <span 
class="hljs-title">testQueryUser</span><span class="hljs-params">()</span> 
</span>{
+        String userName = <span 
class="hljs-string">&quot;userTest0001&quot;</span>;
+        String userPassword = <span 
class="hljs-string">&quot;userTest0001&quot;</span>;
+        User queryUser = usersService.queryUser(userName, userPassword);
+        logger.info(queryUser.toString());
+        Assert.assertTrue(queryUser != <span class="hljs-keyword">null</span>);
+    }
+
+}
+</code></pre>
+</li>
+</ol>
+</section><footer class="footer-container"><div 
class="footer-body"><div><h3>联系我们</h3><h4>有问题需要反馈?请通过以下方式联系我们。</h4></div><div 
class="contact-container"><ul><li><img class="img-base" 
src="/img/emailgray.png"/><img class="img-change" src="/img/emailblue.png"/><a 
href="/zh-cn/community/development/subscribe.html"><p>邮件列表</p></a></li><li><img 
class="img-base" src="/img/twittergray.png"/><img class="img-change" 
src="/img/twitterblue.png"/><a 
href="https://twitter.com/dolphinschedule";><p>Twitt [...]
+  <script 
src="//cdn.jsdelivr.net/npm/[email protected]/dist/react-with-addons.min.js"></script>
+  <script 
src="//cdn.jsdelivr.net/npm/[email protected]/dist/react-dom.min.js"></script>
+  <script>window.rootPath = '';</script>
+  <script src="/build/vendor.e2c05dc.js"></script>
+  <script src="/build/blog.md.79dd3d0.js"></script>
+  <script>
+    var _hmt = _hmt || [];
+    (function() {
+      var hm = document.createElement("script");
+      hm.src = "https://hm.baidu.com/hm.js?4e7b4b400dd31fa015018a435c64d06f";;
+      var s = document.getElementsByTagName("script")[0];
+      s.parentNode.insertBefore(hm, s);
+    })();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/zh-cn/blog/ut-template.json b/zh-cn/blog/ut-template.json
new file mode 100644
index 0000000..5f237af
--- /dev/null
+++ b/zh-cn/blog/ut-template.json
@@ -0,0 +1,6 @@
+{
+  "filename": "ut-template.md",
+  "__html": "<h1>DolphinScheduler 单元测试模版</h1>\n<h2>1. 
单元测试原则</h2>\n<ol>\n<li>\n<p><strong>3A 原则</strong></p>\n<p>3A 
原则简单易懂,它为套件中的所有测试提供了统一的结构,这种统一的结构是其最大的优势之一:一旦习惯了这种模式,就可以更轻松地阅读和理解测试,这反过来又降低了整个测试套件的维护成本。</p>\n<ul>\n<li>Arrange:初始化测试数据。</li>\n<li>Act:调用被测方法,传入依赖参数并获取返回值。</li>\n<li>Assert:断言,对返回值做出断言。</li>\n</ul>\n<p>示例:</p>\n<pre><code
 class=\"language-java\"><span class=\"hljs-keyword\">public</span> <span 
class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span 
class=\"hljs [...]
+  "link": "/dist/zh-cn/blog/ut-template.html",
+  "meta": {}
+}
\ No newline at end of file

Reply via email to