On this page
使用 Apache Shiro 进行测试
文档的此部分说明了如何在单元测试中启用 Shiro。
测试须知
正如我们已经在Subject reference中介绍的那样,我们知道 Subject 是“当前执行中”用户的特定于安全性的视图,并且 Subject 实例始终绑定到线程,以确保我们知道谁在任何时候都在执行逻辑线程执行期间的时间。
这意味着必须始终发生三件事,以支持能够访问当前正在执行的 Subject:
必须创建一个
Subject
实例Subject
实例必须“绑定”到当前正在执行的线程。线程完成执行之后(或者如果线程的执行结果为
Throwable
),则Subject
必须为“未绑定” *,以确保线程在任何线程池化的环境中保持“干净”。
Shiro 的体系结构组件可为正在运行的应用程序自动执行此绑定/取消绑定逻辑。例如,在 Web 应用程序中,根 Shiro 过滤器在过滤请求时执行此逻辑。但是,由于测试环境和框架不同,我们需要针对所选测试框架自己执行此绑定/解除绑定逻辑。
Test Setup
因此,我们知道在创建Subject
实例后,必须将其绑定到线程。在线程(或本例中为测试)完成执行之后,我们必须* unbind * Subject 以保持线程“干净”。
幸运的是,现代测试框架(如 JUnit 和 TestNG)本身已经支持“设置”和“拆卸”这一概念。我们可以利用这种支持来模拟 Shiro 在“完整”应用程序中将执行的操作。我们已经创建了一个基础抽象类,您可以在下面的自己的测试中使用它-可以随意复制和/或修改。它可以用于单元测试和集成测试(在此示例中,我们使用的是 JUnit,但是 TestNG 也可以工作):
AbstractShiroTest
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.LifecycleUtils;
import org.apache.shiro.util.ThreadState;
import org.junit.AfterClass;
/**
* Abstract test case enabling Shiro in test environments.
*/
public abstract class AbstractShiroTest {
private static ThreadState subjectThreadState;
public AbstractShiroTest() {
}
/**
* Allows subclasses to set the currently executing {@link Subject} instance.
*
* @param subject the Subject instance
*/
protected void setSubject(Subject subject) {
clearSubject();
subjectThreadState = createThreadState(subject);
subjectThreadState.bind();
}
protected Subject getSubject() {
return SecurityUtils.getSubject();
}
protected ThreadState createThreadState(Subject subject) {
return new SubjectThreadState(subject);
}
/**
* Clears Shiro's thread state, ensuring the thread remains clean for future test execution.
*/
protected void clearSubject() {
doClearSubject();
}
private static void doClearSubject() {
if (subjectThreadState != null) {
subjectThreadState.clear();
subjectThreadState = null;
}
}
protected static void setSecurityManager(SecurityManager securityManager) {
SecurityUtils.setSecurityManager(securityManager);
}
protected static SecurityManager getSecurityManager() {
return SecurityUtils.getSecurityManager();
}
@AfterClass
public static void tearDownShiro() {
doClearSubject();
try {
SecurityManager securityManager = getSecurityManager();
LifecycleUtils.destroy(securityManager);
} catch (UnavailableSecurityManagerException e) {
//we don't care about this when cleaning up the test environment
//(for example, maybe the subclass is a unit test and it didn't
// need a SecurityManager instance because it was using only
// mock Subject instances)
}
setSecurityManager(null);
}
}
Testing & Frameworks
AbstractShiroTest
类中的代码使用 Shiro 的ThreadState
概念和静态 SecurityManager。这些技术在测试和框架代码中很有用,但很少在应用程序代码中使用。
大多数需要确保线程状态一致性的与 Shiro 合作的最终用户,几乎都会使用 Shiro 的自动 Management 机制,即 Subject.associateWith 和 Subject.execute 方法。这些方法在主题线程关联的参考中介绍。
Unit Testing
单元测试主要是关于测试您的代码,而仅是在有限范围内的代码。考虑到 Shiro 时,您 true 要关注的是您的代码可以与 Shiro 的* API *一起正常工作-您并不需要测试 Shiro 的实现是否正常工作(这是 Shiro 开发团队必须确保的事情)在 Shiro 的代码库中)。
检验 Shiro 的实现是否可以与您的实现一起工作的测试实际上是集成测试(下面讨论)。
ExampleShiroUnitTest
因为单元测试更适合测试您自己的逻辑(而不是您的逻辑可能调用的任何实现),所以“模拟”您逻辑所依赖的任何 API 是一个好主意。这在 Shiro 上非常有效-您可以模拟Subject
接口,并使其反映您希望被测代码对之做出反应的任何条件。我们可以利用诸如EasyMock和Mockito之类的现代模拟框架为我们完成此任务。
但是如上所述,Shiro 测试中的关键是要记住在测试执行期间必须将任何 Subject 实例(模拟或真实)绑定到线程。因此,我们要做的就是绑定模拟主题,以确保事情按预期进行。
(此示例使用 EasyMock,但 Mockito 的效果也一样):
import org.apache.shiro.subject.Subject;
import org.junit.After;
import org.junit.Test;
import static org.easymock.EasyMock.*;
/**
* Simple example test class showing how one may perform unit tests for
* code that requires Shiro APIs.
*/
public class ExampleShiroUnitTest extends AbstractShiroTest {
@Test
public void testSimple() {
//1. Create a mock authenticated Subject instance for the test to run:
Subject subjectUnderTest = createNiceMock(Subject.class);
expect(subjectUnderTest.isAuthenticated()).andReturn(true);
//2. Bind the subject to the current thread:
setSubject(subjectUnderTest);
//perform test logic here. Any call to
//SecurityUtils.getSubject() directly (or nested in the
//call stack) will work properly.
}
@After
public void tearDownSubject() {
//3. Unbind the subject from the current thread:
clearSubject();
}
}
如您所见,我们没有设置 Shiro SecurityManager
实例或配置Realm
或类似的东西。我们只是在创建一个模拟Subject
实例,并通过setSubject
方法调用将其绑定到线程。这样可以确保测试代码或我们正在测试的SecurityUtils.getSubject()
代码中的所有调用均能正常工作。
请注意,setSubject
方法实现会将您的模拟 Subject 绑定到线程,并且将保留在那里,直到您使用其他Subject
实例调用setSubject
或通过clearSubject()
调用从线程中明确清除它为止。
保持主题与线程绑定多长时间(或将其替换为其他测试中的新实例)取决于您和您的测试要求。
tearDownSubject()
该示例中的tearDownSubject()
方法使用 Junit 4 注解,以确保无论执行哪种测试方法,在执行线程后都会从线程中清除 Subject。这要求您设置一个新的Subject
实例,并为每个执行的测试(通过setSubject
)进行设置。
但是,这并非绝对必要。例如,您可以在每个测试的开始(例如,以@Before
Comments 的方法)(通过setSujbect
)绑定一个新的 Subject 实例。但是,如果要执行此操作,则最好使用@After tearDownSubject()
方法保持事物对称和“干净”。
您可以在每种方法中手动混合和匹配此设置/拆卸逻辑,或者使用@Before 和@AfterComments(如果您认为合适)。但是,由于所有测试中的tearDownShiro()
方法中都有@AfterClass
注解,因此AbstractShiroTest
超类将在所有测试后将其从线程中解除绑定。
Integration Testing
既然我们已经介绍了单元测试设置,那么让我们来谈谈集成测试。集成测试是跨 API 边界测试实现。例如,在调用实现 B 时测试实现 A 是否有效,而实现 B 则按预期进行测试。
您也可以在 Shiro 中轻松执行集成测试。 Shiro 的SecurityManager
实例及其包装的东西(例如 Realms 和 SessionManager 等)都是非常轻量级的 POJO,它们使用的内存很少。这意味着您可以为执行的每个测试类创建和拆除SecurityManager
实例。当您的集成测试运行时,它们将使用“真实” SecurityManager
和Subject
实例,就像您的应用程序将在运行时使用一样。
ExampleShiroIntegrationTest
下面的示例代码看起来与上面的单元测试示例几乎相同,但是三步过程略有不同:
现在有一个步骤“ 0”,它设置了一个“真实的” SecurityManager 实例。
现在,第 1 步使用
Subject.Builder
构造一个“真实”主题实例,并将其绑定到线程。
线程绑定和取消绑定(步骤 2 和 3)的功能与单元测试示例相同。
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
public class ExampleShiroIntegrationTest extends AbstractShiroTest {
@BeforeClass
public static void beforeClass() {
//0. Build and set the SecurityManager used to build Subject instances used in your tests
// This typically only needs to be done once per class if your shiro.ini doesn't change,
// otherwise, you'll need to do this logic in each test that is different
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:test.shiro.ini");
setSecurityManager(factory.getInstance());
}
@Test
public void testSimple() {
//1. Build the Subject instance for the test to run:
Subject subjectUnderTest = new Subject.Builder(getSecurityManager()).buildSubject();
//2. Bind the subject to the current thread:
setSubject(subjectUnderTest);
//perform test logic here. Any call to
//SecurityUtils.getSubject() directly (or nested in the
//call stack) will work properly.
}
@AfterClass
public void tearDownSubject() {
//3. Unbind the subject from the current thread:
clearSubject();
}
}
如您所见,实例SecurityManager
的具体实现已实例化,并可以通过setSecurityManager
方法在其余的测试中访问。然后,稍后通过getSecurityManager()
方法使用Subject.Builder
时,测试方法可以使用此SecurityManager
。
还要注意,SecurityManager
实例是通过@BeforeClass
设置方法设置的,这对于大多数测试类来说是相当普遍的做法。但是,如果愿意,您可以创建一个新的SecurityManager
实例,并通过任何测试方法随时通过setSecurityManager
对其进行设置-例如,您可以根据测试要求引用两个不同的.ini 文件来构建新的SecurityManager
。
最后,就像单元测试示例一样,AbstractShiroTest
超级类将通过其@AfterClass tearDownShiro()
方法清除所有 Shiro 构件(所有剩余的SecurityManager
和Subject
实例),以确保线程“干净”以供下一个测试类运行。
协助处理文档
尽管我们希望该文档对您使用 Apache Shiro 所做的工作有所帮助,但社区一直在不断改进和扩展文档。如果您想为 Shiro 项目提供帮助,请考虑在需要的地方更正,扩展或添加文档。您提供的每一点帮助都会扩大社区,进而改善 Shiro。
提交文档的最简单方法是通过单击下面的Edit
链接提交请求请求,然后将其发送到User Forum或用户邮件列表。