代码改变世界

【C#|.NET】从细节出发(三) 逻辑层事务和page object模式

2014-07-17 12:29  熬夜的虫子  阅读(1937)  评论(0编辑  收藏  举报

一. 业务逻辑层的事务问题

如果你的程序分层清晰并且系统禁用复杂存储过程,那么在DA中的职责比较单一。程序的逻辑通过BLL调用各种不同模块的DA来实现数据操作。如果当需要不同模块在一个事务的时候,问题就产生了。

如果你在bll引用System.Data...或者你在DA中穿插各种复杂逻辑的时候基本上你的工程已经不能算是好的程序了,

1. 使用TransactionScope

TransactionScope可以使代码块成为事务性代码。但是需要开通MSDTC权限,并且TransactionScope的性能问题也一直存在争议。

2. 阶段性提交

这个涉及到框架层次的修改,会单独开篇幅来说

3. 使用委托 将多模块提交汇聚在一起 和框架层次的阶段性提交殊途同归

这个就是设计上的问题,将其他模块的方法以委托方式传入DA。举个例子假如主线为消费记录ConsumeRecord,分支线为消费详细ConsumeRecordDetail和票据信息TravelTicket。其中TravelTicket完全为另一个服务模块对自己为黑盒。ConsumeRecordDetail对自己为白盒。

  DA层

     public delegate BizResult<string> ArchiveTravelTicketDelegate( DbTransaction transaction, List<string> travelTicketIDList);

  主线服务BLL中,其中StartArchives为其他模块但是需要包含近事务的方法

        private BizResult<string> InsertConsumeRecord(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails)
        {
            return consumeRecordArchiveTargetDA.DoArchive(consumeRecordEntity, consumeRecordDetails, new TravelTicketArchiveService().StartArchives);
        }

  主线服务DA中

     public BizResult<string> DoArchive(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails, 
ArchiveHelper.ArchiveTravelTicketDelegate archiveTravelTicket) { return ArchiveHandle(consumeRecordEntity, consumeRecordDetails, true, ConsumeRecordHandle, archiveTravelTicket); }
        public BizResult<string> ConsumeRecordHandle(DbTransaction currentTransaction, ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails)
        {
            return ConsumeRecordHandle(currentTransaction, consumeRecordEntity, consumeRecordDetails,
                (x, y) => InsertConsumeRecordLog(currentTransaction, consumeRecordEntity, true, false)
                , InsertConsumeRecord, consumeRecordDetailArchiveDA.InsertConsumeRecordDetail);
        }

  其中InsertConsumeRecordLog,InsertConsumeRecord为主线自己的方法,ConsumeRecordDetail因为白盒可以直接使用consumeRecordDetailArchiveDA.InsertConsumeRecordDetail,archiveTravelTicket为黑盒。

   主线BaseDA中

     public BizResult<string> ArchiveHandle(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails, bool isArchive,
ConsumeRecordHandlerDelegate consumeRecordHandlerDelegate, ArchiveHelper.ArchiveTravelTicketDelegate archiveTravelTicket)
        {
            using (DbConnection dbConnection = DbObject.CreateConnection())
            {
                dbConnection.Open();

                BizResult<string> bizResult;
                using (DbTransaction currentTransaction = dbConnection.BeginTransaction())
                {
                    try
                    {
                        bizResult = consumeRecordHandlerDelegate(currentTransaction, consumeRecordEntity, consumeRecordDetails);
                        if (bizResult.IsSuccessful)
                        {
                            if (archiveTravelTicket != null)
                            {
                                bizResult = archiveTravelTicket(currentTransaction,
                                                                consumeRecordDetails.Select(x => x.TravelMoneyID).
                                                                    Distinct().ToList());
                            }
                            if (bizResult.IsSuccessful)
                            {
                                bizResult.Message = "xxx success";
                                currentTransaction.Commit();
                                return bizResult;
                            }
                            bizResult.Message = "xxx fail";
                        }
                        bizResult.Message = string.Format("{0}:{1}", isArchive, bizResult.Message);
                        currentTransaction.Rollback();
                    }
                    catch (Exception ex)
                    {
                        logger.Error(ex);
                        currentTransaction.Rollback();
                        throw;
                    }
                }
                return bizResult;
            }
        }

二. 测试环节的page object模式

  拿自动化测试Selenium为例,一般而言我们对于程序都是应付测试用例,针对测试用例写出一个一个test-method。这样无可厚非,使用page object模式可以让代码开起来更专业更精彩。

  在自动化测试中,主要关注UI的测试互动区域。 Page对象只是这些模型作为测试代码中的对象可以减少了重复的代码量,并且意味着,如果用户界面的变化,也只需要修改一个地方。和软件设计模式中dry(don't repeat yourself)类似。并且在page object模式中,PageObjects也可以实现页于页之间的逻辑。总得来说减少了代码的重复,让测试更具可读性和强大的,提高了测试的可维护性,特别是当有频繁变化的ui变更。

举个列子(java版本)

public class LoginPageTest {
    private LoginPage loginPage;
    private final String username = "xx@163.com";
    private final String pwd= "Welcome1";

    @Before
    public void setUp() throws Exception {
        String applicationRoot = new File(
                getClass().getProtectionDomain().getCodeSource().getLocation().getPath())
                .getParent();
        System.setProperty("webdriver.chrome.driver", applicationRoot + "/chromedriver");
        WebDriver webDriver = new ChromeDriver();
        loginPage = new LoginPage(webDriver);
        loginPage.init();
    }

    @Test
    public void testLoginWithoutFakeCheckbox()
    {
        loginPage.setUserName(username);
        loginPage.setPassword(pwd);
        loginPage.pressLoginButton();
        assertThat(loginPage.getClassByLoginWithoutFakeCheckbox(), equalTo("checkbox-wrapper not-checked"));
    }


    @Test
    public void testLoginWithWrongUsername()
    {
        loginPage.setUserName("wrongusername");
        loginPage.setPassword(pwd);
        loginPage.pressCheckBox();
        loginPage.pressLoginButton();
        assertThat(loginPage.getClassByLoginWithWrongUserName(), equalTo("wrapper-input-text wrong"));
    }

    @Test
    public void testLoginWithNullPwd()
    {
        loginPage.clearInput();
        loginPage.setUserName(username);
        loginPage.pressCheckBox();
        loginPage.pressLoginButton();
        assertThat(loginPage.getClassByLoginWithNullPwd(), equalTo("wrapper-input-text wrong"));
    }

    @Test
    public void testLoginWithRightWay()
    {
        loginPage.clearInput();
        loginPage.setUserName(username);
        loginPage.setPassword(pwd);
        loginPage.pressCheckBox();
        loginPage.pressLoginButton();
        assertThat(loginPage.getTextByRightWay(), equalTo("选择性别"));
    }

}

  

public class LoginPage {
    private final WebDriver webDriver;
    private final WebDriverWait wait;
    private JavascriptExecutor js;
    private final String username = "xx@163.com";
    private final String pwd = "Welcome1";
    private WebElement webElementLogin;
    private WebElement webElementPwd;
    private WebElement webElementInput;

    public LoginPage(WebDriver webDriver) {
        this.webDriver = webDriver;
        this.wait = new WebDriverWait(webDriver, 10);
        this.js = (JavascriptExecutor) webDriver;
    }

    public void init() {
        webDriver.get("http://root:root@localhost:8080/apitool/xxx/owF3PjkBFY5_56Q_KYs5fxmLZTKI");
        WebElement webElementName = wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("p.text.bold.name")));

        if (webElementName != null) {
            System.out.print(webElementName.getText());
        }
        webElementLogin = webDriver.findElement(By.cssSelector("input.input-text.login-input"));
        webElementLogin.sendKeys(username);
        webElementPwd = webDriver.findElement(By.cssSelector("input.input-text.password-input"));
        webElementPwd.sendKeys(pwd);
    }

    public String getClassByLoginWithoutFakeCheckbox() {
        WebElement webElementCss = wait.until(
                ExpectedConditions.visibilityOfElementLocated(
                        By.xpath("//*[contains(@class,'checkbox-wrapper not-checked')]")));
        WebElement webElementHref = webDriver.findElement(By.xpath("//*[@id=\"login-page\"]/div/section/div/div[5]/div/div"));
        return webElementHref.getAttribute("class");
    }

    public String getClassByLoginWithWrongUserName() {
        webElementInput = webDriver.findElement(By.xpath("//*[@id=\"login-page\"]/div/section/div/div[3]"));
        return webElementInput.getAttribute("class");
    }

    public String getClassByLoginWithNullPwd() {
        webElementInput = webDriver.findElement(By.xpath("//*[@id=\"login-page\"]/div/section/div/div[3]"));
        return webElementInput.getAttribute("class");
    }

    public String getTextByRightWay() {
        GenderViewPage genderViewPage = new GenderViewPage(webDriver);
        return genderViewPage.getGenderText();
    }

    public void pressCheckBox() {
        js.executeScript("$('.fake-checkbox').attr('class','fake-checkbox active')");
    }

    public void clearInput() {
        webElementLogin.clear();
        webElementPwd.clear();
    }

    public void setUserName(String username) {
        webElementLogin.clear();
        webElementLogin.sendKeys(username);

    }

    public void setPassword(String password) {
        webElementPwd.clear();
        webElementPwd.sendKeys(pwd);
    }

    public void pressLoginButton() {
        js.executeScript("$('.login-button').trigger('touchstart')");
    }

    public void closeBrowser()
    {
        webDriver.quit();
    }
}

三. sql update 慎用位运算

update t_User set valye = Flag | 4 where UserID = 1

如果Flag字段中存在负数,在做位运算的时候,更新出非常大的数值,超过2的62次方

 

希望对大家有帮助。